Serialization - MSUTeam/MSU GitHub Wiki

Description

The serialization system allows modders to write mods in a safer way compared to working directly with vanilla serialization. With flag serialization it's now much easier to both save data so that it can be loaded with a save, while also making your mod safely removable from that save without causing any issues.

The serialization functions here use the serialize and deserialize functions from the ::MSU.Utils namespace, these allow for the simple (de)serialization of arrays and tables and may also be useful for your mod.

Checking Savegame Version of Mod

<Mod>.Serialization.isSavedVersionAtLeast( _version, _metaData )
// _version is a string
// _metadata is a table

_version is the semantic version we are checking if our mod was on in the savegame.
_metadata is the metadata returned by calling _in.getMetaData() on the _in argument passed to the onDeserialize function.

This function returns true if the loaded savegame had at least _version of our mod when it was saved.

Serialization Emulation

Serialization emulation allows modders to store serialized representations of BB classes in memory. This is useful when trying to clone a bb object, or store it for later retrieval. In MSU there are 2 emulation approaches, the current default is Standard Serialization, which should be used unless you are serializing from/into flags, in which case you should use the older Flag Serialization instead.

Standard Serialization

Using Standard Serialization, data you write is fully validated as you write it (and before it is written to a file if you are using Persistent Data, this means you must use the correct functions for the types you are trying to write. This is to ensure no issues occur when later trying to read the same data. If there is a type conflict when reading, MSU will ::logError that an error has occured, but will allow deserialization to continue. This is because in some cases this might not cause issues (as is the case in a handful of vanilla files), but this should generally be avoided in mods.

SerializationData

SerializationData is a representation of data serialized using the Battle Brothers serialization system. Using the getSerializationEmulator and getDeserializationEmulator functions, it is possible to write to and read from the SerializationData respectively.

Constructor
local serializationData = ::MSU.Class.SerializationData()

When constructed, a MetaDataEmulator is also created for the SerializationData, which is serialized and deserialized alongside the SerializationData.

getSerializationEmulator
<SerializationData>.getSerializationEmulator()

Returns a new SerializationEmulator, with the SerializationData's MetaDataEmulator, writing to the SerializationData.

getDeserializationEmulator
<SerializationData>.getDeserializationEmulator()

Returns a new DeserializationEmulator, with the SerializationData's MetaDataEmulator, reading from the SerializationData.

MetaDataEmulator

A MetaDataEmulator emulates the vanilla game's MetaData object accessible in onSerialize and onDeserialize functions by using the getMetaData method on the storage argument. This means that all functions available in the vanilla MetaData object are available and equivalent to the same functions in the MetaDataEmulator.

MetaDataEmulators are serialized alongside attached SerializationData. This process is a little complicated, but effectively they will try to serialize any data directly written to them (using functions like setString), as well as anything that would have been written to them using the world_state onBeforeSerialize function.

SerializationEmulator

A SerializationEmulator emulates the vanilla game's _out storage object passed to onSerialize functions. This means that all functions available in the vanilla _out object are available and equivalent to the same functions in the SerializationEmulator.

DeserializationEmulator

A DeserializationEmulator emulates the vanilla game's _in storage object passed to onDeserialize functions. This means that all functions available in the vanilla _in object are available and equivalent to the same functions in the DeserializationEmulator.

Standard Serialization Example

local itemToClone = ::new("scripts/items/weapons/named/named_sword");
itemToClone.setName("My Favorite Sword");
local data = ::MSU.Class.SerializationData(); // initialize a new PersistentData
local serEmu = data.getSerializationEmulator(); // create a SerializationEmulator from the PersistentData
itemToClone.onSerialize(serEmu); // serialize the player
serEmu.getMetaData().setString("a", "Success!"); // set a string in the metadata
local newItem = ::new("scripts/items/weapons/named/named_sword"); // created a new player to deserialize the data into
local deserEmu = data.getDeserializationEmulator(); // create a DeserializationEmulator from the PersistentData
newItem.onDeserialize(deserEmu); // deserialize the player
::assert(newItem.getName() == itemToClone.getName()); // assert that the names match
::assert(serEmu.getMetaData().getString("a") == deserEmu.getMetaData().getString("a")); // assert that the string was set in the metadata

Flag Serialization

Flag serialization allows you to easily store any object of your choosing inside of flags so it can be serialized, and then retrieve it during deserialization back into a flag format.
Note: these flags will be cleared after any world_state serialize/deserialize call, so you shouldn't use them to permanently store an object to retrieve at any time.

flagSerialize

<Mod>.Serialization.flagSerialize( _id, _object, _flags = null)
// _id is a string
// _object is any squirrel type except for class/instance/weakref and it cannot be a BB object
// _flags is a tag_collection

_id is a unique ID (for your mod, if a different mod uses the same ID they will not conflict) for this set of _flags.
_object is the object you wish to serialize
_flags is the tag_collection used to store the _object, if null it defaults to ::World.Flags.

This function allows you to store _object inside of _flags with identifier _id. If the _flags are serialized, your _object will be safely serialized together with them, and will be deserialized with the flags when they are deserialized. To retrieve your _object from the _flags use flagDeserialize

<Mod>.Serialization.getSerializationEmulator(_id, _flags = null)
// _id is a string
// _flags is a tag_collection

_id is a unique ID (for your mod, if a different mod uses the same ID they will not conflict) for this set of _flags.
_flags is the tag_collection used to store the _object, if null it defaults to ::World.Flags.

Returns a SerializationEmulator which is a custom object that should be passed to the onSerialize function of BB objects.

This function allows for the serialization of BB objects in flags. It will return an object which emulates the object passed to onSerialize functions of BB objects, instead storing that data in the passed tag_collection. This data can then be read from the tag_collection with a FlagDeserializationEmulator returned by getDeserializationEmulator. Look at the example to see how this is used in practice.

flagDeserialize

<Mod>.Serialization.flagDeserialize( _id, _defaultValue, _object = null, _flags = null)
// _id is a string
// _defaultValue is the same type as _object or the expected return value
// _object is a table, array, BB Object or null
// _flags is a tag_collection

_id is a unique ID (for your mod, if a different mod uses the same ID they will not conflict) for this set of _flags.
_defaultValue is an acceptable default value for the function to return in case the serialized object doesn't exist in _flags for any reason
If _object is a BB Object, flagSerialize will call deserialize on that object, and it will deserialized from the container as though using normal vanilla deserialization.
_flags is the tag_collection in which _object was previously stored, if null it defaults to ::World.Flags.

This function allows you to retrieve an _object previously stored inside of _flags, with identifier _id, using flagSerialize. Returns the object previously serialized using flagSerialize inside of the tag_collection _flags, if the object was a table/array and _object is not null, _object will be used as a base to deserialize the table/array into. If for any reason the value could not be deserialized properly, the return value and _object will be set to _defaultValue.

<Mod>.Serialization.getDeserializationEmulator(_id, _flags = null)
// _id is a string
// _flags is a tag_collection

_id is a unique ID (for your mod, if a different mod uses the same ID they will not conflict) for this set of _flags.
_flags is the tag_collection in which _object was previously stored, if null it defaults to ::World.Flags.

Returns a DeserializationEmulator which is a custom object that should be passed to the onDeserialize function of BB objects.

This function allows for the deserialization of BB objects from flags which had previously been stored with a FlagSerializationEmulator from getSerializationEmulator. It will return an object which emulated the object passed to onDeserialize functions of BB objects, instead reading that data from the specified tag_collection.

FlagSerializationEmulator

A FlagSerializationEmulator emulates the vanilla game's _out storage object passed to onSerialize functions. This means that all functions available in the vanilla _out object are available and equivalent to the same functions in the FlagSerializationEmulator.

FlagDeserializationEmulator

A FlagDeserializationEmulator emulates the vanilla game's _in storage object passed to onDeserialize functions. This means that all functions available in the vanilla _in object are available and equivalent to the same functions in the FlagDeserializationEmulator.

Flag Serialization Example

local hooksMod = ::Hooks.register("mod_my_mod", "2.1.1", "My Mod");
local mymod = ::MSU.Class.Mod("mod_my_mod", "2.1.1", "My Mod");
local myTable = {
	NumberOfTimesSaveLoaded = 0 // a value to keep track of the number of times this save has been loaded with this mod
};
local myVal = "someVal";
local importantPlayer = ::new("scripts/entity/tactical/player"); // a player bb object
importantPlayer.setName("My Favorite Player");

hooksMod.hook("scripts/states/world_state", function(q) {
	q.onSerialize = @(__original) function( _out )
	{
		mymod.Serialization.flagSerialize("MyTable", myTable, ::World.Flags);
		// ::World.Flags is unnecessary in this case, but used here as an example
		mymod.Serialization.flagSerialize("MyVal", myVal);
		
		local serializationEmulator = mymod.Serialization.getSerializationEmulator("MyPlayer");
		importantPlayer.onSerialize(serializationEmulator);
		
		__original(_out);
	}

	q.onDeserialize = @(__original) function( _in )
	{
		__original(_in);
		importantPlayer = ::new("scripts/entity/tactical/player"); // player shouldn't be stored between saved/loads to clean up
		myTable = {
			NumberOfTimesSaveLoaded = 0;
		};
		if (mymod.Serialization.isSavedVersionAtLeast("2.0.0", _in.getMetaData())) // only deserialize if we're certain the previous serialization code has run
		// you should include the above line even if making a new mod (in that case set it to for example 0.1.0)
		{
			local defaultValueTable = myTable // in this case it is okay to have the default value be the same as the table we have already cleared out because it has no data left over from the previous save/load cycle
			mymod.Serialization.flagDeserialize("MyTable", defaultValueTable, myTable);
			myVal = mymod.Serialize.flagDeserialize("MyVal", "someVal"); // here we have our default value be the same as the original value we set myVal to when we started the game
			local deserializationEmulator = myMod.Serialization.getDeserializationEmulator("MyPlayer");
			importantPlayer.onDeserialize(deserializationEmulator)
		}
		myTable.NumberOfTimesSaveLoaded += 1;
	}
});
⚠️ **GitHub.com Fallback** ⚠️