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 Basic rules of serialization and 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.

unsigned int size() const
Returns the number of indexed objects.
Definition: SBCContainerIndexer.hpp:481
virtual void writeUnsignedLongLongElement(const std::string &elementName, unsigned long long element)
Writes an element of unsigned long long integer type with elementName name and value element.
Definition: SBCSerializer.cpp:66
virtual int readIntElement()
Reads an element of integer type.
Definition: SBCSerializer.cpp:132
virtual unsigned long long readUnsignedLongLongElement()
Reads an element of unsigned long long integer type.
Definition: SBCSerializer.cpp:166
This class is the base class for serializers.
Definition: SBCSerializer.hpp:10
virtual void unserialize(SBCSerializer *serializer, const SBNodeIndexer &nodeIndexer, const SBVersionNumber &sdkVersionNumber=SB_SDK_VERSION_NUMBER, const SBVersionNumber &classVersionNumber=SBVersionNumber(1, 0, 0))
Unserializes the node.
Definition: SBMVisualModel.cpp:44
virtual void serialize(SBCSerializer *serializer, const SBNodeIndexer &nodeIndexer, const SBVersionNumber &sdkVersionNumber=SB_SDK_VERSION_NUMBER, const SBVersionNumber &classVersionNumber=SBVersionNumber(1, 0, 0)) const
Serializes the node.
Definition: SBMVisualModel.cpp:38
unsigned int getIndex(SBDDataGraphNode *node) const
Returns the index associated to the node.
Definition: SBDDataGraphNodeIndexer.cpp:107
This class describes a node indexer.
Definition: SBDDataGraphNodeIndexer.hpp:21
virtual float readFloatElement()
Reads an element of float type.
Definition: SBCSerializer.cpp:172
#define SB_FOR(VARIABLE, CONTAINER)
A macro to easily write for loops involving containers.
Definition: SBCContainerFor.hpp:83
This class describes an atom in a structural model.
Definition: SBMStructuralModelNodeAtom.hpp:32
virtual bool readBoolElement()
Reads an element of boolean type.
Definition: SBCSerializer.cpp:102
virtual void writeBoolElement(const std::string &elementName, bool element)
Writes an element of boolean type with elementName name and value element.
Definition: SBCSerializer.cpp:22
virtual unsigned int readUnsignedIntElement()
Reads an element of unsigned integer type.
Definition: SBCSerializer.cpp:138
This class describes a version number.
Definition: SBCContainerVersionNumber.hpp:14
virtual void writeFloatElement(const std::string &elementName, float element)
Writes an element of float type with elementName name and value element.
Definition: SBCSerializer.cpp:70
virtual void writeUnsignedIntElement(const std::string &elementName, unsigned int element)
Writes an element of unsigned integer type with elementName name and value element.
Definition: SBCSerializer.cpp:46
virtual void writeIntElement(const std::string &elementName, int element)
Writes an element of integer type with elementName name and value element.
Definition: SBCSerializer.cpp:42