Web Analytics Made Easy - Statcounter
Skip to content

Serialization#

SAMSON uses serialization, i.e. the conversion of data structures to and from serialized streams, in order to achieve several essential tasks. For example copying, pasting, saving and loading, etc. are all performed through serialization.

Overview#

All data graph nodes defined by the SAMSON API implement a serialize and an unserialize function.

Serialization is performed via a serializer class passed to the serialize and unserialize functions.

Writing serialization and unserialization functions#

If your SAMSON Extension contains new classes derived from data graph nodes types (typically, visual models, interaction models, property models, controllers, labels or state updaters), you might need to re-implement their serialize and unserialize functions, and the isSerializable function to indicate to SAMSON that whether the custom class is serializable or not.

In the re-implementation of the serialize function it is necessary to call first the serialize function of a parent class, which in turn calls the serialize function of its parents and so on all the way up to SBNode itself. The parent functions are needed for correct saving and loading of node properties. This allows to save the node's name, visibility flag, etc. The same is for the unserialize function.

When SAMSON serializes a group of nodes, it proceeds in two passes:

  • First, all nodes that are to be serialized are added to a node indexer. This way, each node that's about to be serialized has an index. It's those indices that are used to indicate which nodes are referenced by other nodes.
  • Then, the serialize function of each node is called. This function receives the node indexer.

During unserialization, SAMSON also proceeds in two steps:

  • First, it creates all required nodes and indexes them in a node indexer, in the same order as during serialization.
  • Then, the unserialize function of each node is called. This function receives the node indexer.

Note

Saving of the document leads to the serialization of every node that can be serialized, while copying leads to the serialization only of copied nodes. Thus, while serializing, it is necessary to check whether the referenced node is present in the node indexer. If a node that is referenced by a serialized node is also serialized then during the serialization it is necessary to indicate the index in the node indexer of the referenced node, else it is necessary to indicate an address of the referenced node.

Please, see the section and the example below for more information.

Basic rules of serialization#

  • Unserialization should be done in the same order as serialization and for the same number of parameters.
  • When you serialize a class and its base class is serializable, you should call the serialization function of the base class first. The same for unserialization.
  • If a node referenced by the serialized node is also to be serialized (e.g. it is a case when saving a document), then serialize the referenced node's index in the nodeIndexer (see the serialize function definition). If a node referenced by the serialized node is not to be serialized (e.g. it is a case when copying a node), then serialize the reference node's address. And serialize a boolean flag for each referenced node indicating whether it was serialized via its index or address.

Example#

Let's consider how to serialize a custom class derived from data graph nodes types on an example of a custom visual model which references data graph nodes.

First, it is necessary to indicate to SAMSON that the custom visual model is serializable. For that, we re-implement the isSerializable function to return true:

bool SECustomVisualModel::isSerializable() const {

    return true;

}

Now, we need to re-implement both serialize and unserialize functions.

The serialize function allows for serialization of a node (a visual model in this case).

void SECustomVisualModel::serialize(SBCSerializer* serializer, 
    const SBNodeIndexer& nodeIndexer, const SBVersionNumber& sdkVersionNumber, 
    const SBVersionNumber& classVersionNumber) const {

    // Serialization of the parent class
    SBMVisualModel::serialize(serializer, nodeIndexer,
        sdkVersionNumber, classVersionNumber);

    // Write visual model parameters
    serializer->writeFloatElement("visualModelParameter1", visualModelParameter1);
    serializer->writeIntElement("visualModelParameter2", visualModelParameter2);
    // ...

    // Write the number of atoms to which the visual model is applied
    serializer->writeUnsignedIntElement("numberOfAtoms", atomIndexer.size());

    unsigned int atomIndex = 0; // the index of the atom in the indexer

    // Write indices of the atoms to which this visual model is applied
    SB_FOR(SBPointer<SBAtom> atom, atomIndexer) {

        if (nodeIndexer.getIndex(atom(), atomIndex)) {

            // the atom is indexed
            serializer->writeBoolElement("atomIsIndexed", true);
            serializer->writeUnsignedIntElement("atomIndex", atomIndex);

        }
        else {

            // the atom is not indexed, 
            // the user must be copying just the visual model
            // so we serialize the atom address itself
            serializer->writeBoolElement("atomIsIndexed", false);
            serializer->writeUnsignedLongLongElement("atomIndex",
                (unsigned long long)atom());

        }

    }

}

First we invoke the serialization function of the parent class.

Then we save information on a custom node itself: custom node's parameters, number of nodes which are referenced by the custom node (e.g. nodes to which the visual model is applied), and the indices of these nodes in the nodeIndexer or their addresses. We check whether the atom itself was serialized or not. Basically, saving leads to the serialization of every node that can be serialized, while copying leads to the serialization only of nodes which are being copied. If atom was serialized we save its index in the nodeIndexer, else we save the atom's address.

The unserialize function allows for unserialization of a node (a visual model in this case).

void SECustomVisualModel::unserialize(SBCSerializer* serializer, 
    const SBNodeIndexer& nodeIndexer, const SBVersionNumber& sdkVersionNumber,
    const SBVersionNumber& classVersionNumber) {

    // Unserialization of the parent class
    SBMVisualModel::unserialize(serializer, nodeIndexer,
        sdkVersionNumber, classVersionNumber);

    // Read visual model parameters
    visualModelParameter1 = serializer->readFloatElement();
    visualModelParameter2 = serializer->readIntElement();
    // ...

    // Read the number of atoms to which this visual model is applied
    unsigned int numberOfAtoms = serializer->readUnsignedIntElement();

    unsigned int atomIndex = 0; // the index of the atom in the indexer

    // Read indices of the atoms to which this visual model is applied and
    // add these node into the atom indexer of the visual model
    for (unsigned int i = 0; i < numberOfAtoms; ++i) {

        atomIsIndexed = serializer->readBoolElement();
        if (atomIsIndexed) {

            // the atom was serialized too
            atomIndex = serializer->readUnsignedIntElement();
            atomIndexer.addReferenceTarget(nodeIndexer[atomIndex]);

        }
        else {

            // the atom was not serialized, it must still exist in memory
            atomIndexer.addReferenceTarget(
                (SBAtom*)serializer->readUnsignedLongLongElement());

        }

    }

}

First, we invoke the unserialization function of the parent class. This will create the node in the data graph. Then we read information on a custom node itself: custom node's parameters, number of nodes referenced by the custom node (e.g. nodes to which the visual model is applied), and the indices of these nodes in the nodeIndexer or their addresses. We check whether the referenced node itself was serialized or not. If the referenced node was serialized we read its index in the nodeIndexer, else we read the referenced node's address.