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.

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

getSerializationEmulator

<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 deserialization emulator passed 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.

getDeserializationEmulator

<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 SerializationEmulator 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.

Example

local mymod = ::MSU.Class.Mod("mymod", "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");

::mods_hookExactClass("states/world_state", function(o)
{
	local onSerialize = o.onSerialize;
	o.onSerialize = 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);
		
		onSerialize(_out);
	}

	local onDeserialize = o.onDeserialize;
	o.onDeserialize = function( _in )
	{
		onDeserialize(_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** ⚠️