C++でデータを保存する(FlatBuffers編)


cocos2d-xでデータを保存するにはUserDefaultかsqlite3を利用していたが、汎用性のあるライブラリがあったのでその調査メモ。

## FlatBuffers

Googleがオープンソース化したC++シリアライズライブラリとして2014/6に公開。
ゲーム開発者のためのライブラリとして高いパフォーマンスを発揮する模様。

C++だけでなくGoやJavaでも利用可能。

C++だとヘッダのみで利用できるので色々な環境に対応できる。

### 特徴

- パーズ/アンパッキングなしにシリアル化されたデータにアクセスできる
- メモリ効率と速度
- 柔軟性
- ライブラリのコード量の少なさ
- 型安全性
- 利便性
- クロスプラットフォーム

RapidJsonはJSONのパーサで高速であるがFlatBuffersは[更に高速](http://google.github.io/flatbuffers/md__benchmarks.html)に動作する模様。

### URL
- [Android Developers Blog](http://android-developers.blogspot.jp/2014/06/flatbuffers-memory-efficient.html)
- [公式ドキュメント](http://google.github.io/flatbuffers/)
- [GitHub](https://github.com/google/flatbuffers)

### C++でFlatBuffersを利用できるようにする

FlatBuffersは以下の手順で利用できる。

* スキーマファイルを作成する
* flatcコマンドでスキーマファイルをコンパイルする
* 生成されたヘッダとflatbuffers.h(場合によってはflatbuffersのidl_parser.cppなどのcppもコンパイルしてリンクする)をincludeする
* FlatBufferBuilderでファイルの読み書きを行なう

#### スキーマファイルをコンパイルできるようにする

- 環境
    - Mac OS X 10.9.4
    - XCode 5.1.1

* [GitHub](https://github.com/google/flatbuffers)からリポジトリをcloneする

```
    git clone https://github.com/google/flatbuffers.git
```

* XCodeでコンパイルする

```
    cd flatbuffers
    open build/Xcode/FlatBuffers.xcodeproj
```

schemeにflatcを選択してビルドする。
flatbuffersディレクトリにflatcというバイナリができる。
以下はflatcのヘルプ

```
usage: ./flatc [OPTION]... FILE... [-- FILE...]
  -b      Generate wire format binaries for any data definitions.
  -t      Generate text output for any data definitions.
  -c      Generate C++ headers for tables/structs.
  -g      Generate Go files for tables/structs.
  -j      Generate Java classes for tables/structs.
  -o PATH Prefix PATH to all generated files.
  -S      Strict JSON: add quotes to field names.
FILEs may depend on declarations in earlier files.
FILEs after the -- must be binary flatbuffer format files.
Output files are named using the base file name of the input,and written to the current directory or the path given by -o.
example: ./flatc -c -b schema1.fbs schema2.fbs data.json
```

#### スキーマファイルを書いてC++用ヘッダを作成する

以下ではサンプルをそのまま利用する。独自クラスでも同様にスキーマを書けば良い。

以下のコマンドでスキーマファイルをコンパイルする。

```
  cd samples
  ../flatc -c monster.fbs
```

monster_generated.hというヘッダが生成されるのでこれをincludeして利用する。

#### データの読み書きをしてみる

JSONはサンプルにあるので、バイナリデータを利用してみる。

flatbuffers関連ヘッダはincludeディレクトリに配置。
monster_generated.hはsamplesディレクトリに配置.

C++のコンパイルコマンドはこんな感じ

```
clang++ -g -Wall -std=c++11 -I include main.cc
```

以下、main.ccのコード。

```cpp:main.cc
#include <iostream>

#include "samples/monster_generated.h"
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/util.h"

int main(int /*argc*/, const char * /*argv*/[]) {
  using namespace MyGame::Sample;

  {
    // 新規にデータを作成
    flatbuffers::FlatBufferBuilder builder;
    auto vec = Vec3(1, 2, 3);
    auto name = builder.CreateString("Test");
    unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    auto inventory = builder.CreateVector(inv_data, 10);
    auto mloc = CreateMonster(builder, &vec, 100, 50, name, inventory, Color_Blue);
    builder.Finish(mloc);

    auto monster = GetMonster(builder.GetBufferPointer());
    printf("### 作成したデータ: hp=%d, mana=%d, name=%s\n",
           monster->hp(), monster->mana(), monster->name()->c_str());

    // データファイル書き込み
    auto ok = flatbuffers::SaveFile("samples/monsterdata.bin",
                                    reinterpret_cast<const char *>(builder.GetBufferPointer()),
                                    builder.GetSize(),
                                    true);
    if (!ok) {
      printf("couldn't save files!\n");
      return 1;
    }
  }

  {
    // データファイル読み込み
    std::string binfile;
    auto ok = flatbuffers::LoadFile("samples/monsterdata.bin", true, &binfile);
    if (!ok) {
      printf("couldn't load files!\n");
      return 1;
    }

    auto monster = GetMonster(binfile.data());
    printf("### ロードしたデータ: hp=%d, mana=%d, name=%s\n",
           monster->hp(), monster->mana(), monster->name()->c_str());
  }

  return 0;

}
```

スキーマに関する更なる情報は、flatbuffers/testsディレクトリにあるmonster_test.fbsを参照