Skip to content

5 Extra: serializable(SKSE)

DK edited this page Sep 20, 2023 · 1 revision

serializable

All in one, painless serialization solution. All serializables are automatically registered to internal handler, and unregistered upon destruction(although probably not needed). All reasonable data types are supported, they are flattened first with internal resolvers, then serialized with SKSE.
Common types:

  • STL containers
  • user defined aggregate types
  • all trivial types
  • string

declaration

Wrap the data type in dku::serializable<>, with the second template parameter giving it a unique hash identifier. This identifier is used in the future for variant updating/backwards compatibility feature.

struct MyDataType
{
    int a;
    bool b;
    char c;
    std::string s;
    std::vector<int> arr;
};
using MyComplexDataType = std::map<RE::FormID, MyDataType>;

// empty
dku::serializable<MyDataType, "SimpleData"> mySimpleData;
// or initializer list
dku::serializable<MyDataType, "SimpleData"> mySimpleData(
    {1, true, 'a', "Elo", {1,2,3,4,5}});
// or assignment
dku::serializable<MyDataType, "SimpleData"> mySimpleData = MyDataType{
    {1, true, 'a', "Elo", {1,2,3,4,5}}};

dku::serializable<MyComplexDataType, "ComplexDataMap"> myDataMap;
// ...

using, modifying

The data can be accessed by prepending the asterisk symbol * or using arrow operator -> as if it were a pointer type, or the getter method get().

for (auto& [id, data] : *myDataMap) {
    // ...
}

myDataMap->emplace(...);

registering

During plugin load, simply call it once and you are done:

DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse)
{
	DKUtil::Logger::Init(Plugin::NAME, REL::Module::get().version().string());
	
	INFO("{} v{} loaded", Plugin::NAME, Plugin::Version);

	// this
	dku::serialization::api::RegisterSerializable();

	return true;
}

You can also pass in optional plugin identifier, although the internal hash function already makes a unique id.

custom resolver

You can attach custom resolver callbacks to the dku::serializable<> types and they'll be called during Save, Load, Revert state, after they are resolved.

dku::serializable<MyComplexDataType, "ComplexDataMap"> myDataMap;
using type = decltype(myDataMap)::type;

// resolver function takes its underlying data
void CustomCallback(type& a_data, dku::serialization::ResolveOrder a_order)
{
    if (a_order == dku::serialization::ResolveOrder::kSave) {
        INFO("Saved");
    } else if (a_order == dku::serialization::ResolveOrder::kLoad) {
        INFO("Loaded");
        for (auto& [f, d] : a_data) {
            INFO("{} {}", f, d.s);
        }
    } else {
        INFO("Reverted");
    }
}

myDataMap.add_resolver(CustomCallback);

flattened layout map

Tweak the preprocessor definitions a bit, define DKU_X_MOCK will redirect all read/write calls to its internal buffer for testing, and generate a flattened layout map of the data type it's operating on.