serialization - MSUTeam/MSU GitHub Wiki
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.
<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 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.
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 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.
local serializationData = ::MSU.Class.SerializationData()
When constructed, a MetaDataEmulator is also created for the SerializationData, which is serialized and deserialized alongside the SerializationData.
<SerializationData>.getSerializationEmulator()
Returns a new SerializationEmulator, with the SerializationData's MetaDataEmulator, writing to the SerializationData.
<SerializationData>.getDeserializationEmulator()
Returns a new DeserializationEmulator, with the SerializationData's MetaDataEmulator, reading from the SerializationData.
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.
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.
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.
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 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.
<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.
<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.
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.
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.
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;
}
});