libcppa  Version 0.1
announce_example_5.cpp

An advanced example for announce implementing serialization for a user-defined tree data type.

/******************************************************************************
 * This example shows how to implement serialize/deserialize to announce      *
 * non-trivial data structures to the libcppa type system.                    *
 *                                                                            *
 * Announce() auto-detects STL compliant containers and provides              *
 * an easy way to tell libcppa how to serialize user defined types.           *
 * See announce_example 1-4 for usage examples.                               *
 *                                                                            *
 * You should use "hand written" serialize/deserialize implementations        *
 * if and only if there is no other way.                                      *
 ******************************************************************************/

#include <cstdint>
#include <iostream>
#include "cppa/cppa.hpp"
#include "cppa/to_string.hpp"

using std::cout;
using std::endl;

using namespace cppa;

// a node containing an integer and a vector of children
struct tree_node
{
    std::uint32_t value;
    std::vector<tree_node> children;

    tree_node(std::uint32_t v = 0) : value(v)
    {
    }

    tree_node& add_child(int v = 0)
    {
        children.push_back({ v });
        return *this;
    }

    tree_node(const tree_node&) = default;

    // recursively print this node and all of its children to stdout
    void print() const
    {
        // format is: value { children0, children1, ..., childrenN }
        // e.g., 10 { 20 { 21, 22 }, 30 }
        cout << value;
        if (children.empty() == false)
        {
            cout << " { ";
            auto begin = children.begin();
            auto end = children.end();
            for (auto i = begin; i != end; ++i)
            {
                if (i != begin)
                {
                    cout << ", ";
                }
                i->print();
            }
            cout << " } ";
        }
    }

};

// a very primitive tree implementation
struct tree
{
    tree_node root;

    // print tree to stdout
    void print() const
    {
        cout << "tree::print: ";
        root.print();
        cout << endl;
    }

};

// tree nodes are equals if values and all values of all children are equal
bool operator==(const tree_node& lhs, const tree_node& rhs)
{
    return (lhs.value == rhs.value) && (lhs.children == rhs.children);
}

bool operator==(const tree& lhs, const tree& rhs)
{
    return lhs.root == rhs.root;
}

// abstract_uniform_type_info implements all functions of uniform_type_info
// except for serialize() and deserialize() if the template parameter T:
// - does have a default constructor
// - does have a copy constructor
// - does provide operator==
class tree_type_info : public util::abstract_uniform_type_info<tree>
{

 protected:

    void serialize(const void* ptr, serializer* sink) const
    {
        // ptr is guaranteed to be a pointer of type tree
        auto tree_ptr = reinterpret_cast<const tree*>(ptr);
        // serialization always begins with begin_object(name())
        // and ends with end_object();
        // name() returns the uniform type name of tree
        sink->begin_object(name());
        // recursively serialize nodes, beginning with root
        serialize_node(tree_ptr->root, sink);
        sink->end_object();
    }

    void deserialize(void* ptr, deserializer* source) const
    {
        // seek_object() gets the uniform name of the next object in the
        // stream without modifying the deserializer
        std::string cname = source->seek_object();
        // this name has to be our type name
        if (cname != name())
        {
            throw std::logic_error("wrong type name found");
        }
        // ptr is guaranteed to be a pointer of type tree
        auto tree_ptr = reinterpret_cast<tree*>(ptr);
        tree_ptr->root.children.clear();
        // workflow is analogous to serialize: begin_object() ... end_object()
        source->begin_object(cname);
        // recursively deserialize nodes, beginning with root
        deserialize_node(tree_ptr->root, source);
        source->end_object();
    }

 private:

    void serialize_node(const tree_node& node, serializer* sink) const
    {
        // value, ... children ...
        sink->write_value(node.value);
        sink->begin_sequence(node.children.size());
        for (const tree_node& subnode : node.children)
        {
            serialize_node(subnode, sink);
        }
        sink->end_sequence();
    }

    void deserialize_node(tree_node& node, deserializer* source) const
    {
        // value, ... children ...
        auto value = get<std::uint32_t>(source->read_value(pt_uint32));
        node.value = value;
        auto num_children = source->begin_sequence();
        for (size_t i = 0; i < num_children; ++i)
        {
            node.add_child();
            deserialize_node(node.children.back(), source);
        }
        source->end_sequence();
    }

};

int main()
{
    // the tree_type_info is owned by libcppa after this function call
    announce(typeid(tree), new tree_type_info);

    tree t; // create a tree and fill it with some data

    t.root.add_child(10);
    t.root.children.back().add_child(11).add_child(12).add_child(13);

    t.root.add_child(20);
    t.root.children.back().add_child(21).add_child(22);

    /*
        tree t is now:
               0
              / \
             /   \
            /     \
          10       20
         / |\     /  \
        /  | \   /    \
       11 12 13 21    22
    */

    // send a tree to ourselves ...
    send(self(), t);

    // send a vector of trees to ourselves
    typedef std::vector<tree> tree_vector;
    announce<tree_vector>();
    tree_vector tvec;
    tvec.push_back(t);
    tvec.push_back(t);
    send(self(), tvec);

    // receive both messages
    int i = 0;
    receive_while([&]() { return ++i <= 2; })
    (
        // ... and receive it
        on<tree>() >> [](const tree& tmsg)
        {
            // prints the tree in its serialized format:
            // @<> ( { tree ( 0, { 10, { 11, { }, 12, { }, 13, { } }, 20, { 21, { }, 22, { } } } ) } )
            cout << "to_string(last_received()): "
                 << to_string(last_received())
                 << endl;
            // prints the tree using the print member function:
            // 0 { 10 { 11, 12, 13 } , 20 { 21, 22 } }
            tmsg.print();
        },
        on<tree_vector>() >> [](const tree_vector& trees)
        {
            // prints "received 2 trees"
            cout << "received " << trees.size() << " trees" << endl;
            // prints:
            // @<> ( {
            //   std::vector<tree,std::allocator<tree>> ( {
            //     tree ( 0, { 10, { 11, { }, 12, { }, 13, { } }, 20, { 21, { }, 22, { } } } ),
            //     tree ( 0, { 10, { 11, { }, 12, { }, 13, { } }, 20, { 21, { }, 22, { } } } )
            //   )
            // } )
            cout << "to_string: " << to_string(last_received()) << endl;
        }
    );

    return 0;
}