• he/him

Coder, pun perpetrator
Grumpiness elemental
Hyperbole abuser


Tools programmer
Writer-wannabe
Did translations once upon a time
I contain multitudes


(TurfsterNTE off Twitter)


Trans rights
Black lives matter


Be excellent to each other


UE4/5 Plugins on Itch
nte.itch.io/

eniko
@eniko

hello, little gamedev pro tip from me to you: always put a version number in any file your game saves externally. this includes save files but it also comes in real handy in config files since if you change the way your game's configuration work you can use it to invalidate or upgrade a user's previous config!


mrhands
@mrhands

If there's one neat trick I've picked up from the Amazon© Lumberyard™ engine, a horrible product that has thankfully been discontinued, it's the idea of an upgrade() method for your serializable data. As the extremely experienced Ms. Fox rightfully points out, you should always put a version number in your data. With this version number in hand, you can (theoretically) upgrade all user data to the latest version without ever losing data!

For example, my game had a TaskDataModel struct that needed to be loaded from the save file. In version 1, the task had an "alignment" stored as an integer value. But for my own sanity, I wanted to change this to a string value in the save file. So, I wrote an upgrade function for the struct:

template <>
inline bool upgrade(TaskDataModel& to, QJsonObject& data, int32_t dataVersion, Mode mode)
{
	// ChangedAlignmentIdentifier

	if (dataVersion < TaskDataModel::Version::ChangedAlignmentIdentifier)
	{
		if (data.find("alignment") != data.end())
		{
			int32_t alignment = data["alignment"].toInt();
			if (alignment >= 0 &&
				alignment <= 2)
			{
				const char* alignment_ids[] = { "nerd", "cool", "jock" };
				data["alignment"] = alignment_ids[alignment];
			}
			else
			{
				data["alignment"] = "";
			}
		}
		else
		{
			data["alignment"] = "";
		}
	}

	return true;
}

This function reads the old data, extracts the alignment as an integer, and then writes the new alignment as a string value. The new serialization code can then always assume that the alignment is read as a string.

If the incoming struct already has the latest version, we don't need to call this method at all, and this is handled at a higher level of my serialization system. But crucially, we can upgrade from version 1 to version 24 by running each step in sequence! The real version of this function has these steps:

template <>
inline bool upgrade(TaskDataModel& to, QJsonObject& data, int32_t dataVersion, Mode mode)
{
	// ChangedAlignmentIdentifier

	// AddedPosition

	// ChangedSuccessToDifficulty

	// RebalancedDifficulty

	// ChangedLocationIdentifierToEnum

	// RenamedPhaseToShift

	// RenamedPhaseToShift

	// MovedIdentifierToSheet

	// ChangedDiceSlotsToDataModelList

	return true;
}

If I ever publish my game (ha!), this system means that as long as I keep incrementing the version number for the structs, I can continuously make patches while keeping my players' save files intact. 😊


Turfster
@Turfster

I've run into so many problems during the course of my *mumble* long career where things suddenly needed to be changed1 and The Original Legacy "Designers" hadn't even conceived of the possibility that someone would fucking want to change anything down the line, causing so much extra work and so many problems for every-fucking-one2.

Be kind to Future You and anyone that ever needs to use whatever you're building.


1: Because, just like sharks, code that doesn't keep movingevolving will die
2: Except, of course, The Original "Designers", who'd fucked off for greener pastures years ago


You must log in to comment.

in reply to @eniko's post:

in reply to @mrhands's post:

Absolutely did this for 1000xRESIST, a game that we expected to do basically zero post-launch support for because you never know. Picked up the habit while working on mobile live service games.