Savefile Compatibility

Hi everybody,

so I’m looking over my save system. I’m using the good old “<<” overload to serialize my data to an FArchive, for example to save a piece of armor:

bool AArmor::SaveLoadData(FArchive& ar, FString fileVersion)
    Super::SaveLoadData(ar, fileVersion);

    ar << Durability;
    ar << MaxDurability;

    return true;

Now the obvious problem with this approach is that all data has to be written and read in the exact same order, any changes to the data structure immediately break any previously made save files. For my current project, savegame compatibility is pretty important (or at least a very nice to have feature), and I really don’t want to add version checks every time I add or remove a property from an object I need to save/load. After a bit of experimentation, I’m really not sure how to proceed though…

Filesize is not a huge problem, my savefiles are still tiny and I’m not even using any compression etc. So I thought: just save a property identifier and type for every value I write to the archive. When loading I first read the property name, then the type and using the saved type I can load my data without crashing the game. Somehow I then pass the loaded data back to the my object and the object just checks for every required property: “is property xyz in my loaded properties? then go ahead and use it to restore stuff”. I don’t really worry about some stuff not loading back properly. As long as loading old saves doesn’t just crash the game and most objects remain working, I’m happy.

It could look something like this:

when saving:

USaveManager::SaveProperty("myPropertyName", myPropertyValue);

when loading:

// load all properties of this object and store them in my save manager so they can be read with LoadProperty()

float propertyINeedToLoad = 0.0;
USaveManager::LoadProperty("myPropertyName", propertyINeedToLoad);

Now in practice I don’t know how to achieve this, with C++, being strictly typed and all. I don’t want to add dozens of overloads to SaveProperty for every possible type I could pass it. Also, same problem for loading the data back out of the archive. When saving and loading a property, I don’t know which type could possibly be passed. Usually the “<<” operator just needs to be overloaded for every type I want to save, but I don’t know how to take advantage of that.

The obvious solution to me would be to serialize myPropertyValue first, as an FString for example and both SaveProperty and LoadProperties would always assume a string was saved. The issue with this is that 1) I don’t know any good approaches with UE4 to easily serialize arbitrary data without writing it to an archive immediately and 2) this might not be very efficient…

I’m running out of ideas here, what would be a good approach for this? I might be on the wrong path entirely.

I solved this problem in my “Savior” plugin by stopping the use of FArchive << operator and serializing properties manually with UProperty* type reflection to strings formatted as Json text.

Loading can be done in any order and variables can be read from file individually, there’s no crashes in loading ever like the crashes you get when reading << operator out of order… If developers remove a property in a Blueprint then FArchive saves will cause crash on load, with my reflection system it doesn’t matter how much a developer changed his Blueprint it’s always going to be compatible with existing save files.

So… Don’t use FArchive << operator :wink:

Thank you for your input, that’s a bit of a bummer but probably for the best. Json it is then!

Is there any built in functionality to serialize more complex types to Json? I’m mostly thinking TSubclassOf<> objects right now. Or do I have to save classes by their name and restore them that way somehow? I wish there was some in-depth documentation on UE4’s Json capabilities, but I can barely find anything official or detailed on the topic.…ter/index.html

Undocumented, like the large majority of engine source.

I also use it to implement Blueprint “Variant” types, where previously I would serialize the variant from a << operator, I replaced with Json object converter to store all different types in same memory address.