Versioning Serialized Game Save Files

I'm working on a game and want to ensure that save files from older versions can still be loaded in newer versions. How can I handle this using cereal's versioning features?

To handle backward compatibility for serialized game save files using cereal's versioning features:

Step 1: Define a version number for your serializable classes and increment it whenever you make changes that affect serialization:

class Player {
  // ...
};
CEREAL_CLASS_VERSION(Player, 1);

class GameState {
  // ...
};
CEREAL_CLASS_VERSION(GameState, 2);

Step 2: In your serialization functions, use the versioned save/load or serialize function signatures:

template <class Archive>
void Player::serialize(Archive& ar,
  std::uint32_t version) {
  ar(name, level);

  if (version >= 1) {
    ar(health);
  }
}

template <class Archive>
void GameState::serialize(Archive& ar,
  std::uint32_t version) {
  ar(players, score);

  if (version >= 2) {
    ar(difficulty);
  }
}

Step 3: When deserializing, check the version number and handle missing or added fields accordingly. Provide default values for fields that may not exist in older save files:

template <class Archive>
void Player::serialize(Archive& ar,
  std::uint32_t version) {
  ar(name, level);

  if (version >= 1) {
    ar(health);
  } else {
    // Default value for older versions
    health = 100;
  }
}

template <class Archive>
void GameState::serialize(Archive& ar,
  std::uint32_t version) {
  ar(players, score);

  if (version >= 2) {
    ar(difficulty);
  } else {
    // Default value for older versions
    difficulty = 1;
  }
}

Step 4: When loading a save file, cereal will automatically detect and pass the appropriate version number to your serialization functions. Your deserialization logic will handle the version differences.

By following this approach, newer versions of your game will be able to load and handle save files from older versions gracefully. Remember to increment the version number whenever you make changes to the serialization layout of your classes.

A complete example is provided below:

#include <cereal/archives/binary.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
#include <fstream>
#include <iostream>
#include <vector>

class Player {
 public:
  std::string name;
  int level;
  int health;

 private:
  friend class cereal::access;

  template <class Archive>
  void serialize(Archive& ar,
    std::uint32_t version) {
    ar(name, level);

    if (version >= 1) {
      ar(health);
    } else {
      // Default value for older versions
      health = 100;
    }
  }
};

CEREAL_CLASS_VERSION(Player, 1);

class GameState {
 public:
  std::vector<Player> players;
  int score;
  int difficulty;

 private:
  friend class cereal::access;

  template <class Archive>
  void serialize(Archive& ar,
    std::uint32_t version) {
    ar(players, score);

    if (version >= 2) {
      ar(difficulty);
    } else {
      // Default value for older versions
      difficulty = 1;
    }
  }
};

CEREAL_CLASS_VERSION(GameState, 2);

int main() {
  GameState gameState;
  gameState.players.emplace_back(Player{
    "Alice", 10, 80});
  gameState.players.emplace_back(Player{
    "Bob", 20, 70});
  gameState.score = 1000;
  gameState.difficulty = 2;

  {
    std::ofstream file("savegame.dat");
    cereal::BinaryOutputArchive archive(file);
    archive(gameState);
  }

  GameState loadedGameState;

  {
    std::ifstream file("savegame.dat");
    cereal::BinaryInputArchive archive(file);
    archive(loadedGameState);
  }

  std::cout << "Loaded Game State:\n";
  std::cout << "Score: "
    << loadedGameState.score << "\n";
  std::cout << "Difficulty: "
    << loadedGameState.difficulty << "\n";
  std::cout << "Players:\n";
  for (const auto& player :
    loadedGameState.players) {
    std::cout << "  " << player.name
      << " (Level " << player.level
      << ", Health " << player.health << ")\n";
  }
}
Loaded Game State:
Score: 1000
Difficulty: 2
Players:
  Alice (Level 10, Health 80)
  Bob (Level 20, Health 70)

Binary Serialization using Cereal

A detailed and practical tutorial for binary serialization in modern C++ using the cereal library.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Serializing Vectors of Custom Types
How can I serialize a vector of my custom Player objects using cereal?
Serializing Maps of Polymorphic Objects
I have a std::map where the value type is a pointer to the base Character class, but it actually points to derived Monster objects. How can I serialize and deserialize this map correctly?
Serializing Private Class Members with Cereal
I want to serialize private member variables of my Player class using cereal. How can I accomplish this?
Serializing Non-Default-Constructible Classes with Cereal
I have a Weapon class that requires parameters in its constructor and doesn't have a default constructor. How can I serialize and deserialize objects of this class using cereal?
Serializing Enum Classes with Cereal
I have an enum class called State in my game. How can I serialize and deserialize variables of this enum class type using cereal?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant