-
Notifications
You must be signed in to change notification settings - Fork 156
Serialization
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 your plugin, which SKSE will use to call your plugin when serializing to/from the co-save. Give it a four letter signature that's a shorthand for your plugin name (i.e.'PLGN'
). -
SetRevertCallback
: -
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 bufferbuf
with the number of byteslength
to the co-save under the signaturetype
with the versionversion
. -
OpenRecord
: This opens a record in the co-save with the given signaturetype
and the versionversion
. It returns a boolean indicating success. -
WriteRecordData
: This writes the bufferbuf
with the number of byteslength
to 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 byteslength
into the given bufferbuf
from the co-save. It returns the number of bytes actually read. -
ResolveHandle
: This takes a virtual machine handlehandle
as 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 formIDformId
as 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 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)
{
int num = 42;
std::vector<int> arr;
for (int 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 {
UInt32 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;
}
}
}
}
}
- The Load Callback
#include "skse64/PluginAPI.h" // SKSESerializationInterface
#include <vector> // vector
void LoadCallback(SKSESerializationInterface* a_intfc)
{
int num;
std::vector<int> 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_':
length = a_intfc->ReadRecordData(&length, sizeof(length));
for (UInt32 i = 0; i < length; ++i) {
int 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;
}
}
}
You'll notice in these callbacks, that we aren't doing something like a_intfc->WriteRecordData(&arr, arr.size());
. The problem with this statement is that the pointer we passed as the buffer points to the std::vector
structure and not the underlying data itself, where the int
's are stored. This approach also doesn't take into account the size of an int
. Remember that we're telling the interface to serialize a number of bytes from the provided buffer, so we must convert out int
into a size using sizeof
and multiply that by the number of elements in the container. A correct way to serialize the vector in a single statement would be a_intfc->WriteRecordData(arr.data(), arr.size() * sizeof(int));
, however, this approach constrains the layout of our data. Consider a structure struct Data { int num1, num2, num3; };
, which we directly serialize using the preceding statement. This would work fine, until we wanted to reorder our member variables so that our structure now looks like struct Data { int num3, num2, num1; };
. Now when we go to load our data, we will be loading what should be num1
into num3
and vice-versa. Thus, it's better to serialize your data one element at a time, so as to give yourself the flexibility to adjust the layout of your structures.