Serialization - Ryan-rsm-McKenzie/CommonLibSSE GitHub Wiki
SKSESerializationInterface can be accessed from SKSEInterface by calling SKSEInterface::QueryInterface(kInterface_Serialization). The serialization interface allows plugin authors to serialize data to the SKSE co-save. This can be useful if the author wishes to persist data between runs of the executable.
-
version: This is the version of the exported interface. Plugin authors should assert on this field if they require a certain version. -
SetUniqueID: This sets a unique signature for the plugin, which SKSE will use to call it plugin when serializing to/from the co-save. Give it a four letter signature that's a shorthand for the plugin name (i.e.'PLGN'). -
SetRevertCallback: This assigns the function that will be called whenever the game swaps between save profiles. -
SetSaveCallback: This assigns the function that will be called whenever the game saves. -
SetLoadCallback: This assigns the function that will be called whenever the game loads. -
SetFormDeleteCallback: This assigns the function that will be called whenever a form is deleted. -
WriteRecord: This writes the bufferbufwith the number of byteslengthto the co-save under the signaturetypewith the versionversion. -
OpenRecord: This opens a record in the co-save with the given signaturetypeand the versionversion. It returns a boolean indicating success. -
WriteRecordData: This writes the bufferbufwith the number of byteslengthto the co-save. It returns a boolean indicating success. -
GetNextRecordInfo: This reads the next record's info from the co-save, storing the signature intype, the version inversion, and the number of bytes inlength. It returns a boolean indicating success. -
ReadRecordData: This reads the specified number of byteslengthinto the given bufferbuffrom the co-save. It returns the number of bytes actually read. -
ResolveHandle: This takes a virtual machine handlehandleas it was when the save was made and writes the handle as it is when the save is loaded intohandleOut. It returns a boolean indicating success. -
ResolveFormId: This takes a formIDformIdas it was when the save was made and writes the formID as it is when the save is loaded intoformIdOut. It returns a boolean indicating success.
- Authors should pass a pointer to the beginning of the data that they wish to serialize as the
bufparameter, and pass the size of that data, in bytes, as thelengthparemeter. For example, givenSInt32 num, one would callSKSESerializationInterface::WriteRecordData(&num, sizeof(num))in order to serialize the value stored innum. - Authors should take care that they are serializing the data they actually wish to serialize. For example, given
SInt32* numone might callSKSESerializationInterface::WriteRecordData(&num, sizeof(num)), however this would serialize the address ofnum, and not the value itself.numis a pointer, and as such, it stores a the address of an integer value. If one wishes to serialize that value, they can simply pass the value in the pointer itself as our buffer. One must also take care that they are taking the size of the type pointed to, and not the size of the pointer.sizeof(num)is equivalent tosizeof(SInt32*)which is not necessarily equivalent tosizeof(SInt32). Thus, the proper call to serializenumwould look likeSKSESerializationInterface::WriteRecordData(num, sizeof(SInt32)). - Authors should take care to serialize fixed-width data types. For example, instead of using
intauthors should useSInt32, which is a 32-bit, signed integer equivalent toint.
Authors should define one function for each callback they wish to register, matching the declared typedef for EventCallback. In these callbacks, authors can use the passed SKSESerializationInterface* to perform the serialization tasks that are required for their plugin.
- The Save Callback
#include "skse64/PluginAPI.h" // SKSESerializationInterface
#include <vector> // vector
void SaveCallback(SKSESerializationInterface* a_intfc)
{
SInt32 num = 42;
std::vector<SInt32> arr;
for (std::size_t i = 0; i < 10; ++i) {
arr.push_back(i);
}
if (!a_intfc->WriteRecord('NUM_', 1, &num, sizeof(num))) {
_ERROR("Failed to serialize num!");
}
if (!a_intfc->OpenRecord('ARR_', 1)) {
_ERROR("Failed to open record for arr!");
} else {
std::size_t size = arr.size();
if (!a_intfc->WriteRecordData(&size, sizeof(size))) {
_ERROR("Failed to write size of arr!");
} else {
for (auto& elem : arr) {
if (!a_intfc->WriteRecordData(&elem, sizeof(elem))) {
_ERROR("Failed to write data for elem!");
break;
}
}
}
}
}In this example, we're attempting to save an integer num and the contents of a vector arr. In the case of num, we use WriteRecord to open a record and write its value to the co-save. In the case of arr, we call OpenRecord to open a record for the array, and WriteRecordData to serialize the number of elements in the array. Finally, we iterate over the contents of the array and serialize them one at a time.
- The Load Callback
#include "skse64/PluginAPI.h" // SKSESerializationInterface
#include <vector> // vector
void LoadCallback(SKSESerializationInterface* a_intfc)
{
SInt32 num;
std::vector<SInt32> arr;
UInt32 type;
UInt32 version;
UInt32 length;
while (a_intfc->GetNextRecordInfo(&type, &version, &length)) {
switch (type) {
case 'NUM_':
if (!a_intfc->ReadRecordData(&num, sizeof(num))) {
_ERROR("Failed to load num!");
}
break;
case 'ARR_':
{
std::size_t size = a_intfc->ReadRecordData(&size, sizeof(size));
for (UInt32 i = 0; i < size; ++i) {
SInt32 elem;
if (!a_intfc->ReadRecordData(&elem, sizeof(elem))) {
_ERROR("Failed to load elem!");
break;
} else {
arr.push_back(elem);
}
}
}
break;
default:
_ERROR("Unrecognized signature type!");
break;
}
}
}In this example, we're attempting to load an integer num and the contents of a vector arr. We call GetNextRecordInfo in a while loop and switch-case on the extracted record types to deserialize from the file. In the case of num, we call ReadRecordData directly with num to deserialize its value. In the case of arr, we first deserialize the size of the array from the file, and then insert its elements into the vector, one at a time.
Here is a complete implementation of a plugin using SKSESerializationInterface.
- CommonLib wraps the SKSE interface by using reference types instead of pointer types where convenient, and passes your plugin handle for you automatically.
Here is a complete implementation of a plugin using CommonLib's SKSE::SerializationInterface.