General-Purpose Object Serialization in Lumberyard

The excellent team over at Third Kind Games recently published a blog article providing an example of serializing an object in Lumberyard. They expressed some concern that the methods used might rely on some deprecated or legacy functionality.

This article is simply a follow-up to provide an example of how to reflect and serialize objects in Lumberyard using some of the newer tools.

Lumberyard features a reflection system. Reflection is technically split across various contexts (serialization, editing, behavior, etc), but this article only intends to demonstrate serialization.

Example class:

class MyClass
{
  int m_someInt;
float m_someFloat;
AZStd::string m_someString;
};

This class can now be reflected. Typically Lumberyard uses the pattern of embedding a static Reflect() function within reflected classes. But classes can be reflected from anywhere, assuming public access.

// Reflect MyClass
serializeContext->Class<MyClass>()
->Field("SomeInt", &MyClass::m_someInt)
->Field("SomeFloat", &MyClass::m_someFloat);

This class can now be serialized to/from any data stream. If writing directly to disk, for example, we can use a FileStream. Note that file streams are also compatible with Lumberyard’s VFS (Virtual file System).

AZ::IO::FileStream fileStream("@assets@/myclass.bin", AZ::IO::OpenMode::ModeWrite);
if (fileStream.Open())
{
MyClass instanceOfMyClass;
...
AZ::Utils::SaveObjectToStream<MyClass>(fileStream, AZ::DataStream::ST_JSON, &instanceOfMyClass);
fileStream.Close();
}

Note we’re serializing to JSON here. Simply swap in ST_XML or ST_BINARY to write the respective format. Note that regardless of the written format, the ObjectStream auto-discovers when reading. Code that reads in your objects doesn’t have to know or care how they were written . This means you can maintain human-readable formats during development, and easily swap to binary at ship by changing a single enum in one place.

Now to serialize our object back in from a stream. Data is type-checked as well, so null will be returned if the stream does not contain the object type requested.

// Create from stream.
AZ::IO::FileStream fileStream("@assets@/myclass.bin", AZ::IO::OpenMode::ModeRead);
if (fileStream.Open())
{
MyClass* instanceofMyClass = AZ::Utils::LoadObjectFromStream<MyClass>(fileStream);
...
delete instanceOfMyClass;
fileStream.Close();
}
// Or load in-place.
AZ::IO::FileStream fileStream("@assets@/myclass.bin", AZ::IO::OpenMode::ModeRead);
if (fileStream.Open())
{
MyClass instanceofMyClass;
AZ::Utils::LoadObjectFromStreamInPlace<MyClass>(fileStream, instanceOfMyClass);
...
fileStream.Close();
}

Several additional utilities are present for loading/saving in place, to in-memory buffers, as well as for writing/reading multiple objects linearly to/from a stream.

Lumberyard reflection/serialization also supports versioning, allowing classes to evolve over time without breaking data compatibility.

There’s a whole lot more in there to play with. If you want to see more, swing over to the Lumberyard site and give a try! And as always, send us all of your unfiltered feedback. We thrive on it.