NeL has a complex serialization system which was inspired by the serialization system provided in Java. At the root of this system is the concept that serializable classes implement a common method that can be relied upon to import and export data from a variety of sources and ultimately marshall a new object. The serialization system is much more than converting an object to a flat-file and a flat-file back into an object. The serialization system actually harnesses the stream system so that it serializes an object into a binary data buffer in memory so that the serialized object can be handled in whichever way you as a developer deems appropriate. You can write your own stream implementation or use any of the stock ones, such as writing to a flat-file, but you can also harness the abilities of NLNET to send the object across the wire from client to server and vice versa.
The first step to enabling a class to be serializable is to ensure that it implements the serial method. Take the following example:
class CFoo
{
public:
std::string SomeString;
void serial(IStream &stream)
{
stream.serial(SomeString);
}
};
This may seem too simple to be true - but this is essentially what is necessary to enable a class to be serializable. Note that the object we serialized into the stream was a string. With the NeL systme you can only default-serialize primitives such as strings, integers, and so on. Any complex types you must write serial methods for. Assume that the above example contained an instance of the class CBar and you wanted that to be serialized. You would need to ensure that CBar also had a serial method and that it was listed in the serial method of the CFoo class.
The enum primitives must be serialized using the serialEnum or serialShortEnum methods instead of the standard serial template method. The serialEnum method will serialize the enum value to an sint32 value while the serialShortEnum method will serialize the enum value to an uint8 value.
There are some exceptions to the primitives rule and the main, and most important, exception is that of containers. The stream interface provides methods to serialize containers and specialty containers. You can serialize most any STL containers using the streams interface, see this example:
class CBar
{
public:
std::string SomeString;
std::vector<std::string> ABunchOfStrings;
void serial(IStream &stream)
{
stream.serial(SomeString);
stream.serialCont(ABunchOfStrings);
}
};
That summarizes what is needed to work with the vast majority of serialization situations. Beyond this you will need to understand how to serialize pointers, polymorphic objects and hierarchical objects.
When you encounter a pointer in your code that you would like to serialize whether it is a primitive or a non-primitive class or structure you will need to approach it slightly differently. NeL needs to be called with the serialPtr and serialContPtr methods so that it knows not to serialize the value of the object being passed (which may, in most cases, be the address of the object) but the actual object. NeL will output a value corresponding with a pointer and the data and in the event that the pointer is NULL NeL will output a zero (0). See this quick example:
class CBarPtrExample
{
public:
CBar *MyBar;
std::vector<CFoo *> ABunchOfFoos;
void serial(IStream &stream)
{
stream.serialPtr(MyBar);
stream.serialCont(ABunchOfFoos);
}
};
NeL is intelligent in that while serializing data it will keep track of references to objects, preventing circular references as well as preventing duplicate work in the event that two objects reference the same object. Each time a pointer is de-referenced for writing NeL checks a table of the previous pointers processed and bypasses writing the data. At read-time the data structures are faithfully reconstructed.
There are going to be many instances in which you are working with an interface that a concrete object implements with no knowledge of what that concrete implementation is. NeL provides a system that will be discussed in detail later in this handbook for working generating classes. To understand the polymorphic capabilities of NeL serialization a very brief primer is required in NeL's classable system.
NeL has an interface called IClassable and two macros: NLMISC_DECLARE_CLASS and NLMISC_REGISTER_CLASS. For serializable objects you will actually implement IStreamable which extends IClassable. By implementing IStreamable and declaring/registering your class you can now generate your concrete object with a simple factory call.
class IBaseClass : public NLMISC::IStreamable
{
public:
// This is a pure virtual polymorphic class/method.
virtual void foo() = 0;
};
class CSomeClass : public IBaseClass
{
public:
uint32 TestInt;
virtual void foo() { return; }
void serial(NLMISC::IStream &stream)
{
stream.serial(TestInt);
}
NLMISC_DECLARE_CLASS(CSomeClass);
};
class CExampleClass
{
public:
IBaseClass *MyClass;
void serial(IStream &stream)
{
stream.serialPolyPtr(MyClass);
}
};
void main()
{
// ...
NLMISC_REGISTER_CLASS(CSomeClass);
// ...
}
The example above may seem convoluded at first - especially if you are unfamiliar with the classable system. We first created an interface that extends the IStreamable class which allows the serialization and stream system to manage this object as well as making the object capable of being generated by the classable class factory, this allows NeL to instantiate a new object of the concrete type by name, regardless of the interface that is being used. Next we create a concrete implementation of IBaseClass and a simple example class that serializes our new class. You'll see that we also used a new serialization method: serialPolyPtr. This method informs NeL that the object being serialized is polymorphic and that it should reference the classable system to identify the actual concrete type before serializing the object into a stream.
Todo
Todo