FlatBuffers 使用之(二):数据的写入与读取

写一个FlatBuffer Schema


https://github.com/google/flatbuffers到这里下载文件。

要开始使用flatbuffe,你要先写一个schema文件。详细写法点这里。


// Example IDL file for our monster's schema.
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
}
table Weapon {
  name:string;
  damage:short;
}
root_type Monster;

生成Monster的schema


在你写好FlatBuffers schema之后,你要使用工具去生成对应语言所需的代码。

工具在build目录下。使用vs或xcode编译就可以了。

这里以c#为例,对应的命令为:

cd flatbuffers/sample
./../flatc --csharp samples/monster.fbs。


读写monster flatbuffers


我们已经为我们所需的语言编译了schema,我们可以开始创建monster了,并且可通过FlatBuffers以序列化和反序列化他们。

开始创建FlatBuffers

引入命名空间:

using FlatBuffers;
using MyGame.Sample; // The `flatc` generated files. (Monster, Vec3, etc.)
创建一个FlatBufferBuilder的实例,这个实例将包含缓存。

// Create a `FlatBufferBuilder`, which will be used to create our
// monsters' FlatBuffers.
var builder = new FlatBufferBuilder(1);

创建builder之后,我们可以开始序列化我们的数据。在我们写入Monster之前,我们先创建一些武器:剑和斧子。

var weaponOneName = builder.CreateString("Sword");
var weaponOneDamage = 3;
var weaponTwoName = builder.CreateString("Axe");
var weaponTwoDamage = 5;
// Use the `CreateWeapon()` helper function to create the weapons, since we set every field.
var sword = Weapon.CreateWeapon(builder, weaponOneName, (short)weaponOneDamage);
var axe = Weapon.CreateWeapon(builder, weaponTwoName, (short)weaponTwoDamage);

下面我们来创建我们的Monster,一个兽人。他的属性是:红色(表示愤怒),300的HP,坐标(1.0,2.0,3.0),几把可选武器(这里就用我们之前创建的剑和斧子)。然后给他装备上攻击力较高的斧子。还有我们再给他一些在被击败时可能掉落的物品。

在我们序列化monster之前。我们要先序列化他里面的对象。我们这里先序列化名字和掉落物品。

// Serialize a name for our monster, called "Orc".
var name = builder.CreateString("Orc");
// 创建一个矢量来表示会掉落的物品,
// 每一个数字都可以和一个道具通信。在兽人死后,对应的道具就会被声明。
//注意:我们放入byte的顺序,和取出来的时候是反的(比如我先放入的是9,但是数组里第9个才是9)
Monster.StartInventoryVector(builder, 10);
for (int i = 9; i >= 0; i--)
{
  builder.AddByte((byte)i);
}
var inv = builder.EndVector();

我们引用了序列化名字和凋落物品的返回值。这个值是序列化数据的偏移(offset),它描述了数据储存在了哪里。下面我们为monster添加字段的时候,我们会再用到它们。

如果我们要创建一个嵌套的对象(比如table,string,或者其它矢量)的矢量,先把他们的offset放到一个临时的数据结构里,然后创建一个包含这里矢量的额外的矢量。

看下这个例子:

var weaps = new Offset[2];
weaps[0] = sword;
weaps[1] = axe;
// Pass the `weaps` array into the `CreateWeaponsVector()` method to create a FlatBuffer vector.
var weapons = Monster.CreateWeaponsVector(builder, weaps);

创建一个struct,使用flatc生成的Vec3类。

// Create a `Vec3`, representing the Orc's position in 3-D space.
var pos = Vec3.CreateVec3(builder, 1.0f, 2.0f, 3.0f);

我们已经为兽人的非标量的部分进行了序列化,所以我们现在可以开始序列化monster本身:

// Create our monster using `StartMonster()` and `EndMonster()`.
Monster.StartMonster(builder);
Monster.AddPos(builder, pos);
Monster.AddHp(builder, (short)300);
Monster.AddName(builder, name);
Monster.AddInventory(builder, inv);
Monster.AddColor(builder, Color.Red);
Monster.AddWeapons(builder, weapons);
Monster.AddEquippedType(builder, Equipment.Weapon);
Monster.AddEquipped(builder, axe.Value); // Union类型包含两个字段。
var orc = Monster.EndMonster(builder);
buffer已经建好。下面来完成它:

// Call `Finish()` to instruct the builder that this monster is complete.
builder.Finish(orc.Value); // You could also call `Monster.FinishMonsterBuffer(builder, orc);`.


现在buffer已经可以被存在任何地方,发送到网络,被压缩,或者你想对它做的任何事。

下面我写一个存储的例子:

using (var ms = new MemoryStream(builder.DataBuffer.Data, builder.DataBuffer.Position, builder.Offset))
{
     var data = ms.ToArray();
     File.WriteAllBytes(@"Resources/monsterdata_cstest.mon",data);
}


读取兽人FlatBuffers

using FlatBuffers;
using MyGame.Sample; // The `flatc` generated files. (Monster, Vec3, etc.)


读文件(这里是从硬盘读取,你也可以从网络等途径)

var data = File.ReadAllBytes(@"Resources/monsterdata_test.mon");
var bb = new ByteBuffer(data);
var monster = Monster.GetRootAsMonster(bb);

你可以直接能过访问器来访问数据

// For C#, unlike other languages support by FlatBuffers, most values (except for
// vectors and unions) are available as propreties instead of asccessor methods.
var hp = monster.Hp
var mana = monster.Mana
var name = monster.Name
var pos = monster.Pos
var x = pos.X
var y = pos.Y
var z = pos.Z

我们可以能过编号来访问物品矢量里的元素。你也可以拿到矢量的长度。

int invLength = monster.InventoryLength;
var thirdItem = monster.GetInventory(2);

矢量的table:

int weaponsLength = monster.WeaponsLength;
var secondWeaponName = monster.GetWeapons(1).Name;
var secondWeaponDamage = monster.GetWeapons(1).Damage;

Union类型的获取

var unionType = monster.EquippedType;
if (unionType == Equipment.Weapon) {
  var weapon = (Weapon)monster.GetEquipped(new Weapon()); // Requires explicit cast
                                                          // to `Weapon`.
  var weaponName = weapon.Name;     // "Axe"
  var weaponDamage = weapon.Damage; // 5
}

修改Flatbuffers

如果你在调用flac的时候使用了--gen-mutable,你就可以修改了。

var monster = Monster.GetRootAsMonster(buf);
monster.MutateHp(10);            // Set table field.
monster.Pos.MutateZ(4);          // Set struct field.
monster.MutateInventory(0, 1);   // Set vector element.
Mutate方法会返回一个bool值来告诉你是否修改成功。

注意:如果你在创建的时候没有写入某个字段的值,或者写入的值和默认值是相等的。那么这个值其实是没有被储存的,而是储存在代码内,所以无法修改这个值,mutate方法也会返回false。如果你希望默认值也被储存到buffer里,那你需要调用ForceDefaults 方法。c#中没有这个方法!


你可能感兴趣的:(FlatBuffers)