-
Notifications
You must be signed in to change notification settings - Fork 47
Serialization
SceneSerializer can be used to store/restore a set of SceneObjects. Most of the built-in SO types are supported. Store/Restore functions can also be registered with the Scene.TypeRegistry, which allows for serialization of custom SO types.
Each custom SO needs its own SOType. These are declared like so:
public static class MyCustomSOTypes
{
static readonly public SOType MyCustomType =
new SOType("CustomSO", Type.GetType("mynamespace.CustomSO"), null);
}
namespace mynamespace {
public class CustomSO : SomeOtherSO
{
override public SOType Type {
get { return MyCustomSOTypes.MyCustomType; }
}
}
}
Note that custom SO types are used in various other places inside F3. It is not strictly necessary to have a SOType for each SO class, it's only necessary if you need to serialize custom data, or identify the type. For example you could have subclasses of DMeshSO that are still just meshes. You can serialize them by re-using SOTypes.DMesh. However, unless you register custom SOTypes, then on de-serialization, you will get back instances of DMeshSO, not your subclass.
SceneSerializer.Store(IOutputStream o, FScene scene)
is the top-level function that actually serializes the scene. This function iterates over scene.SceneObjects, and if the SO is not marked as Temporary, attempts to store it.
To find custom Store functions, it checks in scene.TypeRegistry for a SOEmitSerializationFunc that is associated with the current SO. This delegate is declared as
public delegate bool SOEmitSerializationFunc(SceneSerializer serializer, IOutputStream o, SceneObject so);
To add custom serialization for an SOType, You register your SOEmitSerializationFunc implementation (and SOBuildFunc, see below) with the registry, like so:
scene.TypeRegistry.RegisterType(MyCustomSOTypes.MyCustomType , EmitFunctionImpl, BuildFunctionImpl);
The built-in SO emitters are in the extensions class SceneSerializerEmitTypesExt. You can use these as examples for how to emit your custom SO type. If you are subclassing a built-in SO type, you can also use functions in this class to emit the base-class data. And, there are utility functions like EmitTransform, EmitMaterial, EmitMeshAscii, etc, which store built-in complex data types, which the de-serialization code already knows how to interpret.
SceneSerializer.Restore(IInputStream i, FScene scene)
is the top-level function for restoring a scene from a serialized version. This is quite a bit more complex than serialization.
Unlike IOutputStream, which has functions for writing various strings and data types, IInputStream has a set of event handlers which are called as the data stream is parsed and different objects are encountered. The SceneSerializer registers various internal functions to respond to these handlers. Note that if you want to implement custom deserialization, you can bypass SceneSerializer and listen to these events yourself.
During deserialization, SceneSerializer has an internal state machine/stack, and so invalid/unknown serializations can be handled cleanly, up to a point. For example if I try to deserialize a stream that contains records for SOTypes that are not in the TypeRegistry, they will simply be ignored.
The SceneSerializer.ISceneObjectFactory must be set before deserializing. Each time the "end" of a SceneObject record is encountered, ISceneObjectFactory.Build is called to convert the record into an actual SO instance.
The SOFactory class provides a default implementation that can rebuild the built-in SO types. These functions may be useful as a guideline for implementing your own SO builders.
If you register an SOBuildFunc for your SOType with the TypeRegistry (as shown above), then it will be called when records with your SO type identifier are found. The delegate for the SOBuildFunc is
public delegate SceneObject SOBuildFunc(SOFactory factory, FScene scene, TypedAttribSet attributes)
The TypedAttribSet contains Attributes that were interpreted from the stream, stored as string/object pairs. In your builder, you have to handle any type conversion/checking/etc. To simplify this, SOFactory contains functions with names like safe_set_property_f(), which will only try to set an attribute of the SO if the property is parsed correctly.
As an more low-level alternative to registering SOBuildFunc types, you can also subclass SOFactory and override the BuildSOFromType() method. This may be necessary if, for example, you would like to build different SO types based on some property other than the type identifier string.
IOutputStream is an interface that provides the functions that SceneSerializer uses to write data. XMLOutputStream is provided as a default implementation. IInputStream parses a serialized stream, XMLInputStream is also provided.
Much of the serialization is based on strings that are assumed to be consistent between write and read. These strings are defined in the IOStrings static class. To serialize your own types/attributes/etc, you will have to define your own constant strings. Make sure they are different than the built-in strings!
IOutputStream implementations can serialize attributes of different types (eg int, bool, array-of-Vector3d, etc). We assume this may be serialized as a string of numbers, a binary buffer, etc. So, at restore time, we need to know the type to interpret the attribute, and this happens before any (custom or built-in) SO-type-handlers have a chance to run. So, we use a string-name-prefix convention to specify the buffer type. See IOStrings for details.
The geometry3Sharp library that F3 is built on contains a static class gSerialization that can Store/Restore many G3 data structures to/from a binary stream using BinaryReader/BinaryWriter. You may find this class useful to serialize your own complex data structures. The function ``IOutputStream.AddAttribute(string sName, byte[] buffer)``` can be used to store an arbitrary block of bytes in a TypedAttribSet.