A Simple Solution to Game Serialization Nightmares

If you’re a game programmer, you’ve almost certainly written code to save the state of a game to disk. Basically, your code has to convert every important aspect of a game entity in a format that makes it easy to load the entity again. This is called Serialization. I’ve written many games, and this problem always seems trivial to start when the entity being serialized is not too complex as shown in this code snippet:

// assume the file stream is already open etc..
void Entity::SaveToFile()
{
  Filer.Write(this->ID); // save the entity ID
  Filer.Write(this->Position); // save the position of the entity
}

void Entity::ReadFromFile()
{
  ID = Filer.ReadInteger();       // read the entity ID
  Position = Filer.ReadVector3(); //read the position of the entity
}

No problem right? This is easy enough but this method breaks down if your game entities are constantly evolving as you get further in development… which is almost always the case in a complex game. If you add another property to your Game Entity that must be saved, you have to create a new save format and you break any levels that have been previously saved. The easy solution is to add version numbers to your save/load routines, but this means after a couple of iterations your code looks something like this:

// assume the file stream is already open etc..
void Entity::SaveToFile()
{
   int version = 3;
   Filer.Write(version); // save version number
   Filer.Write(this->ID); // save the entity ID
   Filer.Write(this->Position); // save the position of the entity
   Filer.Write(this->Name ); // save the name of the entity
}
void Entity::ReadFromFile()
{
   version = Filer.ReadInteger();
   ID = Filer.ReadInteger();
   Position = Filer.ReadVector3();
   if ( version ==3 )
     Name = Filer.ReadString();
   else if ( version == 2 )
   {
      ReadInt(); // read an old deprecated property
      Name = "DefaultName";
   }
   else if ( version ==1 )
      Name = "DefaultName";
}

Not pretty, and this is a simple example. A typical game will have dozens of different entity types or components, and you have to maintain new version numbers every time you add anything to your file format. This is a nightmare if you have to maintain backwards compatibility with your old levels.

Thankfully, smart people have solved this problem before, and thankfully I read a book written by one of these smart people. The solution is to use key-value pairs instead of just writing the values themselves to disk. So instead of just writing numbers to disk, you would write something like the following file to disk:

ID=1234
Name="My Entity Name"
Position= 18.2,3.4,1.6

Then when loading the data, you read the ‘keys’ (eg. ID,Name, Position), and determine if you have a use for them in your current format. For example, if we got rid of the “Position” property, we would just simply ignore it the next time we read and old level from disk. You can easily encapsulate all these concepts into a separate Property class, that holds the name, type, and address information of each property. So when you’re all done your code looks like:

Entity::Init()
{
      PropertyTable.Add("Object ID",&ID);
      PropertyTable.Add("Name", &Name );
      PropertyTable.Add("Position", &Position );
}

Entity::SaveToStream()
{
      PropertyTable.SaveToStream();
}

Entity::LoadFromFile()
{
      PropertyTable.LoadFromStream();
}

This code is easier to read, and more importantly MUCH easier to maintain. You can add/remove properties at will, and you don’t have to maintain version numbers. As long as each property’s key is unique, you will be fine. Another nice side effect of this system, is you can use the key/value pairs as data for your in-game tweaking system. In our games, clicking on a game entity automatically brings up it’s properties and allows the level designer to change them at runtime.

Our level editor. The properties on the right are edit and saving-friendly.

Performance

Yes saving everything as key/value pairs is slower and more memory intensive. But usually the difference is not noticeable during development, and is worth the amount of time you’ll save not dealing with save-game version issues. When your game is ready to ship, you simply ignore the keys and save the values in binary format as you always have, with no performance hit to speak of.
Happy Coding!