セーブデータ

こんにちはプログラマのマツノブです。

今回もプログラムの話をしていきましょう。

みなさん、セーブデータ構造体の構成変更によって容量が変わり互換性が失われ残念な気持ちになったことありませんか?

私はあります。

セーブデータ構造体1

struct SaveData {
    struct Character {
        int money;
        
        int hp;
        
        bool flag[50];
    };
    
    Character character[CHARACTER_MAX];
};

セーブデータ構造体2

struct SaveData {
    struct Character {
        int money;
        
        int hp;
        
        bool flag[60];
        
        int mp
    };
    
    Character character[CHARACTER_MAX];
};

セーブデータ構造体1で実装していたのに後々flagの数が変更されたりmpのパラメータが追加されたりして構造体のサイズが変わるとセーブデータバイナリから正しくコピーできなくなり互換性が失われます。

今回はそんなセーブデータ構造体の構成変更による容量変化を最小限に抑える方法について考えていきましょう。

#include <type_traits>

template<typename T, int max_size>
struct SaveDataPadding : public T {
public:
        static const int SAVE_DATA_MAX_SIZE = max_size;

private:

	static const int _BUFF_SIZE_ = SAVE_DATA_MAX_SIZE - sizeof(T);

	char _buffer[_BUFF_SIZE_] { };

	static_assert(_BUFF_SIZE_ >= 0, "buffer size error");

	static_assert(max_size % alignof(T) == 0, "alignment size error");

	static_assert(std::is_trivially_copyable<T>::value, "base is not trivially copyable");
};

今回は実に短いコードですね。

struct SaveData {
	struct Character {
		int money;
		
		int hp;
		
		bool flag[50];
	};
	
	SaveDataPadding<Character, 100> character[CHARACTER_MAX];
};

と定義してやればSaveDataPaddingに最大サイズからCharacter構造体のサイズを引いたバッファーが作られ、Character構造体のサイズが必ず100byteになります。

なのでCharacter構造体の末尾に100byteの範囲でパラメータを追加しても後ろのCharacter構造体に影響を与えることがなくなります。

さらにセーブデータとして使う前提の構造体なので幾つかのアサートを仕込んでおきます。

static_assert(_BUFF_SIZE_ >= 0, "buffer size error");

バッファーサイズが0未満になるとバッファーが構築できないのでアサート。

そもそも char _buffer[_BUFF_SIZE_] がサイズエラーになるので必要はないかもしれないですね。

static_assert(max_size % alignof(T) == 0, "alignment size error");

最大サイズをアライメントサイズで割ってあまりが出るということはコンパイラがこちらが関知しないパディングを入れるのでアサート。

static_assert(std::is_trivially_copyable<T>::value, "base is not trivially copyable");

セーブデータはmemcpyなどのメモリコピー関数によってコピーすることが多いのでコピー可能であることを確認します。

これでセーブデータの互換性が失われロード画面でエラーが発生する頻度が下がるでしょう。

それでは皆さん良いプログラミングライフを

Follow me!