64 KiB
Tutorial
Overview
This tutorial provides a basic example of how to work with [FlatBuffers](@ref flatbuffers_overview). We will step through a simple example application, which shows you how to:
- Write a FlatBuffer
schema
file. - Use the
flatc
FlatBuffer compiler. - Parse JSON files that conform to a schema into FlatBuffer binary files.
- Use the generated files in many of the supported languages (such as C++, Java, and more.)
During this example, imagine that you are creating a game where the main
character, the hero of the story, needs to slay some orc
s. We will walk
through each step necessary to create this monster type using FlatBuffers.
Please select your desired language for our quest: \htmlonly
C++ Java C# Go Python JavaScript PHP C \endhtmlonly\htmlonly
\endhtmlonly
Where to Find the Example Code
Samples demonstating the concepts in this example are located in the source code
package, under the samples
directory. You can browse the samples on GitHub
here.
For your chosen language, please cross-reference with:
Writing the Monsters' FlatBuffer Schema
To start working with FlatBuffers, you first need to create a schema
file,
which defines the format for each data structure you wish to serialize. Here is
the schema
that defines the template for our monsters:
// 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;
As you can see, the syntax for the schema
Interface Definition Language (IDL)
is similar to those of the C family of languages, and other IDL languages. Let's
examine each part of this schema
to determine what it does.
The schema
starts with a namespace
declaration. This determines the
corresponding package/namespace for the generated code. In our example, we have
the Sample
namespace inside of the MyGame
namespace.
Next, we have an enum
definition. In this example, we have an enum
of type
byte
, named Color
. We have three values in this enum
: Red
, Green
, and
Blue
. We specify Red = 0
and Blue = 2
, but we do not specify an explicit
value for Green
. Since the behavior of an enum
is to increment if
unspecified, Green
will receive the implicit value of 1
.
Following the enum
is a union
. The union
in this example is not very
useful, as it only contains the one table
(named Weapon
). If we had created
multiple tables that we would want the union
to be able to reference, we
could add more elements to the union Equipment
.
After the union
comes a struct Vec3
, which represents a floating point
vector with 3
dimensions. We use a struct
here, over a table
, because
struct
s are ideal for data structures that will not change, since they use
less memory and have faster lookup.
The Monster
table is the main object in our FlatBuffer. This will be used as
the template to store our orc
monster. We specify some default values for
fields, such as mana:short = 150
. All unspecified fields will default to 0
or NULL
. Another thing to note is the line
friendly:bool = false (deprecated);
. Since you cannot delete fields from a
table
(to support backwards compatability), you can set fields as
deprecated
, which will prevent the generation of accessors for this field in
the generated code. Be careful when using deprecated
, however, as it may break
legacy code that used this accessor.
The Weapon
table is a sub-table used within our FlatBuffer. It is
used twice: once within the Monster
table and once within the Equipment
enum. For our Monster
, it is used to populate a vector of tables
via the
weapons
field within our Monster
. It is also the only table referenced by
the Equipment
enum.
The last part of the schema
is the root_type
. The root type declares what
will be the root table for the serialized data. In our case, the root type is
our Monster
table.
More Information About Schemas
You can find a complete guide to writing schema
files in the
[Writing a schema](@ref flatbuffers_guide_writing_schema) section of the
Programmer's Guide. You can also view the formal
[Grammar of the schema language](@ref flatbuffers_grammar).
Compiling the Monsters' Schema
After you have written the FlatBuffers schema, the next step is to compile it.
If you have not already done so, please follow
[these instructions](@ref flatbuffers_guide_building) to build flatc
, the
FlatBuffer compiler.
Once flatc
is built successfully, compile the schema for your language:
See [flatcc build instructions](https://github.com/dvidelabs/flatcc#building).
Please be aware of the difference between `flatc` and `flatcc` tools.
For a more complete guide to using the flatc
compiler, please read the
[Using the schema compiler](@ref flatbuffers_guide_using_schema_compiler)
section of the Programmer's Guide.
Reading and Writing Monster FlatBuffers
Now that we have compiled the schema for our programming language, we can start creating some monsters and serializing/deserializing them from FlatBuffers.
Creating and Writing Orc FlatBuffers
The first step is to import/include the library, generated files, etc.
using namespace MyGame::Sample; // Specified in the schema.
</div>
<div class="language-java">
~~~{.java}
import MyGame.Sample.*; //The `flatc` generated files. (Monster, Vec3, etc.)
import com.google.flatbuffers.FlatBufferBuilder;
Generated by flatc
.
import MyGame.Sample.Color import MyGame.Sample.Equipment import MyGame.Sample.Monster import MyGame.Sample.Vec3 import MyGame.Sample.Weapon
</div>
<div class="language-javascript">
~~~{.js}
// The following code is for JavaScript module loaders (e.g. Node.js). See
// below for a browser-based HTML/JavaScript example of including the library.
var flatbuffers = require('/js/flatbuffers').flatbuffers;
var MyGame = require('./monster_generated').MyGame; // Generated by `flatc`.
//--------------------------------------------------------------------------//
// The following code is for browser-based HTML/JavaScript. Use the above code
// for JavaScript module loaders (e.g. Node.js).
<script src="../js/flatbuffers.js"></script>
<script src="monster_generated.js"></script> // Generated by `flatc`.
// Contains the `*.php` files for the FlatBuffers library and the `flatc` generated files.
$paths = array(join(DIRECTORY_SEPARATOR, array($root_dir, "php")),
join(DIRECTORY_SEPARATOR, array($root_dir, "samples", "MyGame", "Sample")));
foreach ($paths as $path) {
$file = join(DIRECTORY_SEPARATOR, array($path, $class . ".php"));
if (file_exists($file)) {
require($file);
break;
}
}
}
</div>
<div class="language-c">
~~~{.c}
#include "monster_builder.h" // Generated by `flatcc`.
// Convenient namespace macro to manage long namespace prefix.
#undef ns
#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema.
// A helper to simplify creating vectors from C-arrays.
#define c_vec_len(V) (sizeof(V)/sizeof((V)[0]))
Now we are ready to start building some buffers. In order to start, we need
to create an instance of the FlatBufferBuilder
, which will contain the buffer
as it grows:
After creating the builder
, we can start serializing our data. Before we make
our orc
Monster, lets create some Weapon
s: a Sword
and an Axe
.
auto weapon_two_name = builder.CreateString("Axe"); short weapon_two_damage = 5;
// Use the CreateWeapon
shortcut to create Weapons with all the fields set.
auto sword = CreateWeapon(builder, weapon_one_name, weapon_one_damage);
auto axe = CreateWeapon(builder, weapon_two_name, weapon_two_damage);
</div>
<div class="language-java">
~~~{.java}
int weaponOneName = builder.createString("Sword")
short weaponOneDamage = 3;
int weaponTwoName = builder.createString("Axe");
short weaponTwoDamage = 5;
// Use the `createWeapon()` helper function to create the weapons, since we set every field.
int sword = Weapon.createWeapon(builder, weaponOneName, weaponOneDamage);
int axe = Weapon.createWeapon(builder, weaponTwoName, weaponTwoDamage);
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);
</div>
<div class="language-go">
~~~{.go}
weaponOne := builder.CreateString("Sword")
weaponTwo := builder.CreateString("Axe")
// Create the first `Weapon` ("Sword").
sample.WeaponStart(builder)
sample.Weapon.AddName(builder, weaponOne)
sample.Weapon.AddDamage(builder, 3)
sword := sample.WeaponEnd(builder)
// Create the second `Weapon` ("Axe").
sample.WeaponStart(builder)
sample.Weapon.AddName(builder, weaponTwo)
sample.Weapon.AddDamage(builder, 5)
axe := sample.WeaponEnd(builder)
Create the first Weapon
('Sword').
MyGame.Sample.Weapon.WeaponStart(builder) MyGame.Sample.Weapon.WeaponAddName(builder, weapon_one) MyGame.Sample.Weapon.WeaponAddDamage(builder, 3) sword = MyGame.Sample.Weapon.WeaponEnd(builder)
Create the second Weapon
('Axe').
MyGame.Sample.Weapon.WeaponStart(builder) MyGame.Sample.Weapon.WeaponAddName(builder, weapon_two) MyGame.Sample.Weapon.WeaponAddDamage(builder, 5) axe = MyGame.Sample.Weapon.WeaponEnd(builder)
</div>
<div class="language-javascript">
~~~{.js}
var weaponOne = builder.createString('Sword');
var weaponTwo = builder.createString('Axe');
// Create the first `Weapon` ('Sword').
MyGame.Sample.Weapon.startWeapon(builder);
MyGame.Sample.Weapon.addName(builder, weaponOne);
MyGame.Sample.Weapon.addDamage(builder, 3);
var sword = MyGame.Sample.Weapon.endWeapon(builder);
// Create the second `Weapon` ('Axe').
MyGame.Sample.Weapon.startWeapon(builder);
MyGame.Sample.Weapon.addName(builder, weaponTwo);
MyGame.Sample.Weapon.addDamage(builder, 5);
var axe = MyGame.Sample.Weapon.endWeapon(builder);
$weapon_two_name = $builder->createString("Axe"); $axe = \MyGame\Sample\Weapon::CreateWeapon($builder, $weapon_two_name, 5);
// Create an array from the two Weapon
s and pass it to the
// CreateWeaponsVector()
method to create a FlatBuffer vector.
$weaps = array($sword, $axe);
$weapons = \MyGame\Sample\Monster::CreateWeaponsVector($builder, $weaps);
</div>
<div class="language-c">
~~~{.c}
ns(Weapon_ref_t) weapon_one_name = flatbuffers_string_create_str(B, "Sword");
uint16_t weapon_one_damage = 3;
ns(Weapon_ref_t) weapon_two_name = flatbuffers_string_create_str(B, "Axe");
uint16_t weapon_two_damage = 5;
ns(Weapon_ref_t) sword = ns(Weapon_create(B, weapon_one_name, weapon_one_damage));
ns(Weapon_ref_t) axe = ns(Weapon_create(B, weapon_two_name, weapon_two_damage));
Now let's create our monster, the orc
. For this orc
, lets make him
red
with rage, positioned at (1.0, 2.0, 3.0)
, and give him
a large pool of hit points with 300
. We can give him a vector of weapons
to choose from (our Sword
and Axe
from earlier). In this case, we will
equip him with the Axe
, since it is the most powerful of the two. Lastly,
let's fill his inventory with some potential treasures that can be taken once he
is defeated.
Before we serialize a monster, we need to first serialize any objects that are contained there-in, i.e. we serialize the data tree using depth-first, pre-order traversal. This is generally easy to do on any tree structures.
// Create a vector
representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
unsigned char treasure = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto inventory = builder.CreateVector(treasure, 10);
</div>
<div class="language-java">
~~~{.java}
// Serialize a name for our monster, called "Orc".
int name = builder.createString("Orc");
// Create a `vector` representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
byte[] treasure = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int inv = Monster.createInventoryVector(builder, treasure);
// Create a vector
representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
// Note: Since we prepend the bytes, this loop iterates in reverse order.
Monster.StartInventoryVector(builder, 10);
for (int i = 9; i >= 0; i--)
{
builder.AddByte((byte)i);
}
var inv = builder.EndVector();
</div>
<div class="language-go">
~~~{.go}
// Serialize a name for our monster, called "Orc".
name := builder.CreateString("Orc")
// Create a `vector` representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
// Note: Since we prepend the bytes, this loop iterates in reverse.
sample.MonsterStartInventoryVector(builder, 10)
for i := 9; i >= 0; i-- {
builder.PrependByte(byte(i))
}
int := builder.EndVector(10)
Create a vector
representing the inventory of the Orc. Each number
could correspond to an item that can be claimed after he is slain.
Note: Since we prepend the bytes, this loop iterates in reverse.
MyGame.Sample.Monster.MonsterStartInventoryVector(builder, 10) for i in reversed(range(0, 10)): builder.PrependByte(i) inv = builder.EndVector(10)
</div>
<div class="language-javascript">
~~~{.js}
// Serialize a name for our monster, called 'Orc'.
var name = builder.createString('Orc');
// Create a `vector` representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
var treasure = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var inv = MyGame.Sample.Monster.createInventoryVector(builder, treasure);
// Create a vector
representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
$treasure = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
$inv = \MyGame\Sample\Monster::CreateInventoryVector($builder, $treasure);
</div>
<div class="language-c">
~~~{.c}
// Serialize a name for our monster, called "Orc".
// The _str suffix indicates the source is an ascii-z string.
flatbuffers_string_ref_t name = flatbuffers_string_create_str(B, "Orc");
// Create a `vector` representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
flatbuffers_uint8_vec_ref_t inventory;
// `c_vec_len` is the convenience macro we defined earlier.
inventory = flatbuffers_uint8_vec_create(B, treasure, c_vec_len(treasure));
We serialized two built-in data types (string
and vector
) and captured
their return values. These values are offsets into the serialized data,
indicating where they are stored, such that we can refer to them below when
adding fields to our monster.
Note: To create a vector
of nested objects (e.g. table
s, string
s, or
other vector
s), collect their offsets into a temporary data structure, and
then create an additional vector
containing their offsets.
For example, take a look at the two Weapon
s that we created earlier (Sword
and Axe
). These are both FlatBuffer table
s, whose offsets we now store in
memory. Therefore we can create a FlatBuffer vector
to contain these
offsets.
// Pass the weaps
array into the createWeaponsVector()
method to create a FlatBuffer vector.
int weapons = Monster.createWeaponsVector(builder, weaps);
</div>
<div class="language-csharp">
~~~{.cs}
var weaps = new Offset<Weapon>[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);
To create a struct
, use the Vec3
class/struct that was generated by
the schema compiler:
We have now serialized the non-scalar components of the orc, so we can serialize the monster itself:
// Finally, create the monster using the CreateMonster
helper function
// to set all fields.
auto orc = CreateMonster(builder, &pos, mana, hp, name, inventory, Color_Red,
weapons, Equipment_Weapon, axe.Union());
</div>
<div class="language-java">
~~~{.java}
// Create our monster using `startMonster()` and `endMonster()`.
Monster.startMonster(builder);
Monster.addPos(builder, pos);
Monster.addName(builder, name);
Monster.addColor(builder, Color.Red);
Monster.addHp(builder, (short)300);
Monster.addInventory(builder, inv);
Monster.addWeapons(builder, weapons);
Monster.addEquippedType(builder, Equipment.Weapon);
Monster.addEquipped(builder, axe);
int orc = Monster.endMonster(builder);
// Define an equipment union. create
calls in C has a single
// argument for unions where C++ has both a type and a data argument.
ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe));
ns(Monster_create_as_root(B, &pos, mana, hp, name, inventory, ns(Color_Red),
weapons, equipped));
</div>
*Note: Since we are passing `150` as the `mana` field, which happens to be the
default value, the field will not actually be written to the buffer, since the
default value will be returned on query anyway. This is a nice space savings,
especially if default values are common in your data. It also means that you do
not need to be worried of adding a lot of fields that are only used in a small
number of instances, as it will not bloat the buffer if unused.*
<div class="language-cpp">
<br>
If you do not wish to set every field in a `table`, it may be more convenient to
manually set each field of your monster, instead of calling `CreateMonster()`.
The following snippet is functionally equivalent to the above code, but provides
a bit more flexibility.
<br>
~~~{.cpp}
// You can use this code instead of `CreateMonster()`, to create our orc
// manually.
MonsterBuilder monster_builder(builder);
monster_builder.add_pos(&pos);
monster_builder.add_hp(hp);
monster_builder.add_name(name);
monster_builder.add_inventory(inventory);
monster_builder.add_color(Color_Red);
monster_builder.add_weapons(weapons);
monster_builder.add_equipped_type(Equipment_Weapon);
monster_builder.add_equpped(axe);
auto orc = monster_builder.Finish();
~~~{.c} // It is important to pair `start_as_root` with `end_as_root`. ns(Monster_start_as_root(B)); ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); // or alternatively //ns(Monster_pos_add(&pos);
ns(Monster_hp_add(B, hp));
// Notice that Monser_name_add
adds a string reference unlike the
// add_str and add_strn variants.
ns(Monster_name_add(B, name));
ns(Monster_inventory_add(B, inventory));
ns(Monster_color_add(B, ns(Color_Red)));
ns(Monster_weapons_add(B, weapons));
ns(Monster_equipped_add(B, equipped));
// Complete the monster object and make it the buffer root object.
ns(Monster_end_as_root(B));
</div>
Before finishing the serialization, let's take a quick look at FlatBuffer
`union Equipped`. There are two parts to each FlatBuffer `union`. The first, is
a hidden field `_type`, that is generated to hold the type of `table` referred
to by the `union`. This allows you to know which type to cast to at runtime.
Second, is the `union`'s data.
In our example, the last two things we added to our `Monster` were the
`Equipped Type` and the `Equipped` union itself.
Here is a repetition these lines, to help highlight them more clearly:
<div class="language-cpp">
~~~{.cpp}
monster_builder.add_equipped_type(Equipment_Weapon); // Union type
monster_builder.add_equipped(axe); // Union data
After you have created your buffer, you will have the offset to the root of the
data in the orc
variable, so you can finish the buffer by calling the
appropriate finish
method.
The buffer is now ready to be stored somewhere, sent over the network, be compressed, or whatever you'd like to do with it. You can access the buffer like so:
// Allocate and extract a readable buffer from internal builder heap.
// The returned buffer must be deallocated using free
.
// NOTE: Finalizing the buffer does NOT change the builder, it
// just creates a snapshot of the builder content.
buf = flatcc_builder_finalize_buffer(B, &size);
// use buf
free(buf);
// Optionally reset builder to reuse builder without deallocating // internal stack and heap. flatcc_builder_reset(B); // build next buffer. // ...
// Cleanup. flatcc_builder_clear(B);
</div>
#### Reading Orc FlatBuffers
Now that we have successfully created an `Orc` FlatBuffer, the monster data can
be saved, sent over a network, etc. Let's now adventure into the inverse, and
deserialize a FlatBuffer.
This section requires the same import/include, namespace, etc. requirements as
before:
<div class="language-cpp">
~~~{.cpp}
#include "monster_generate.h" // This was generated by `flatc`.
using namespace MyGame::Sample; // Specified in the schema.
import com.google.flatbuffers.FlatBufferBuilder;
</div>
<div class="language-csharp">
~~~{.cs}
using FlatBuffers;
using MyGame.Sample; // The `flatc` generated files. (Monster, Vec3, etc.)
Generated by flatc
.
import MyGame.Sample.Any import MyGame.Sample.Color import MyGame.Sample.Monster import MyGame.Sample.Vec3
</div>
<div class="language-javascript">
~~~{.js}
// The following code is for JavaScript module loaders (e.g. Node.js). See
// below for a browser-based HTML/JavaScript example of including the library.
var flatbuffers = require('/js/flatbuffers').flatbuffers;
var MyGame = require('./monster_generated').MyGame; // Generated by `flatc`.
//--------------------------------------------------------------------------//
// The following code is for browser-based HTML/JavaScript. Use the above code
// for JavaScript module loaders (e.g. Node.js).
<script src="../js/flatbuffers.js"></script>
<script src="monster_generated.js"></script> // Generated by `flatc`.
// Contains the `*.php` files for the FlatBuffers library and the `flatc` generated files.
$paths = array(join(DIRECTORY_SEPARATOR, array($root_dir, "php")),
join(DIRECTORY_SEPARATOR, array($root_dir, "samples", "MyGame", "Sample")));
foreach ($paths as $path) {
$file = join(DIRECTORY_SEPARATOR, array($path, $class . ".php"));
if (file_exists($file)) {
require($file);
break;
}
}
}
</div>
<div class="language-c">
~~~{.c}
// Only needed if we don't have `#include "monster_builder.h"`.
#include "monster_reader.h"
#undef ns
#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema.
Then, assuming you have a variable containing to the bytes of data from disk, network, etc., you can create a monster from this data:
// Deserialize the data from the buffer. auto monster = GetMonster(buffer_pointer);
// monster
is of typeMonster *
, and points to somewhere inside the buffer.
// Note: root object pointers are NOT the same as buffer_pointer
.
</div>
<div class="language-java">
~~~{.java}
// We can access the buffer we just made directly. Pretend this came over a
// network, was read off of disk, etc.
java.nio.ByteBuffer buf = builder.dataBuffer();
// Deserialize the data from the buffer.
Monster monster = Monster.getRootAsMonster(buf);
// Deserialize the data from the buffer. var monster = Monster.GetRootAsMonster(buf);
</div>
<div class="language-go">
~~~{.go}
// We can access the buffer we just made directly. Pretend this came over a
// network, was read off of disk, etc.
buf := builder.FinishedBytes()
// Deserialize the data from the buffer.
monster := sample.GetRootAsMonster(buf, 0)
// Note: We use `0` for the offset here, since we got the data using the
// `builder.FinishedBytes()` method. This simulates the data you would
// store/receive in your FlatBuffer. If you wanted to read from the
// `builder.Bytes` directly, you would need to pass in the offset of
// `builder.Head()`, as the builder actually constructs the buffer backwards.
Deserialize the data from the buffer.
monster = MyGame.Sample.Monster.Monster.GetRootAsMonster(buf, 0)
Note: We use 0
for the offset here, since we got the data using the
builder.Output()
method. This simulates the data you would store/receive
in your FlatBuffer. If you wanted to read from the builder.Bytes
directly,
you would need to pass in the offset of builder.Head()
, as the builder
actually constructs the buffer backwards.
</div>
<div class="language-javascript">
~~~{.js}
// We can access the buffer we just made directly. Pretend this came over a
// network, was read off of disk, etc.
var buf = builder.dataBuffer();
// Deserialize the data from the buffer.
var monster = MyGame.Sample.Monster.getRootAsMonster(buf);
// Deserialize the data from the buffer. $monster = \MyGame\Sample\Monster::GetRootAsMonster($buf);
</div>
<div class="language-c">
~~~{.c}
// Note that we use the `table_t` suffix when reading a table object
// as opposed to the `ref_t` suffix used during the construction of
// the buffer.
ns(Monster_table_t) monster = ns(Monster_as_root(buffer));
// Note: root object pointers are NOT the same as the `buffer` pointer.
If you look in the generated files from the schema compiler, you will see it generated
accessors for all non-deprecated
fields. For example:
These should hold 300
, 150
, and "Orc"
respectively.
Note: The default value 150
wasn't stored in mana
, but we are still able to retrieve it.
To access sub-objects, in the case of our pos
, which is a Vec3
:
// Note: Whenever you access a new object, like in Pos()
, a new temporary
// accessor object gets created. If your code is very performance sensitive,
// you can pass in a pointer to an existing Vec3
instead of nil
. This
// allows you to reuse it across many calls to reduce the amount of object
// allocation/garbage collection.
</div>
<div class="language-python">
~~~{.py}
pos = monster.Pos()
x = pos.X()
y = pos.Y()
z = pos.Z()
x
, y
, and z
will contain 1.0
, 2.0
, and 3.0
, respectively.
Note: Had we not set pos
during serialization, it would be a NULL
-value.
Similarly, we can access elements of the inventory vector
by indexing it. You
can also iterate over the length of the array/vector representing the
FlatBuffers vector
.
For vector
s of table
s, you can access the elements like any other vector,
except your need to handle the result as a FlatBuffer table
:
Last, we can access our Equipped
FlatBuffer union
. Just like when we created
the union
, we need to get both parts of the union
: the type and the data.
We can access the type to dynamically cast the data as needed (since the
union
only stores a FlatBuffer table
).
if (union_type == Equipment_Weapon) {
auto weapon = static_cast<const Weapon*>(monster->equipped()); // Requires static_cast
// to type const Weapon*
.
auto weapon_name = weapon->name()->str(); // "Axe"
auto weapon_damage = weapon->damage(); // 5
}
</div>
<div class="language-java">
~~~{.java}
int unionType = monster.EquippedType();
if (unionType == Equipment.Weapon) {
Weapon weapon = (Weapon)monster.equipped(new Weapon()); // Requires explicit cast
// to `Weapon`.
String weaponName = weapon.name(); // "Axe"
short weaponDamage = weapon.damage(); // 5
}
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
}
</div>
<div class="language-go">
~~~{.go}
// We need a `flatbuffers.Table` to capture the output of the
// `monster.Equipped()` function.
unionTable := new(flatbuffers.Table)
if monster.Equipped(unionTable) {
unionType := monster.EquippedType()
if unionType == sample.EquipmentWeapon {
// Create a `sample.Weapon` object that can be initialized with the contents
// of the `flatbuffers.Table` (`unionTable`), which was populated by
// `monster.Equipped()`.
unionWeapon = new(sample.Weapon)
unionWeapon.Init(unionTable.Bytes, unionTable.Pos)
weaponName = unionWeapon.Name()
weaponDamage = unionWeapon.Damage()
}
}
if union_type == MyGame.Sample.Equipment.Equipment().Weapon:
# monster.Equipped()
returns a flatbuffers.Table
, which can be used to
# initialize a MyGame.Sample.Weapon.Weapon()
.
union_weapon = MyGame.Sample.Weapon.Weapon()
union_weapon.Init(monster.Equipped().Bytes, monster.Equipped().Pos)
weapon_name = union_weapon.Name() // 'Axe'
weapon_damage = union_weapon.Damage() // 5
</div>
<div class="language-javascript">
~~~{.js}
var unionType = monster.equippedType();
if (unionType == MyGame.Sample.Equipment.Weapon) {
var weapon_name = monster.equipped(new MyGame.Sample.Weapon()).name(); // 'Axe'
var weapon_damage = monster.equipped(new MyGame.Sample.Weapon()).damage(); // 5
}
if ($union_type == \MyGame\Sample\Equipment::Weapon) { $weapon_name = $monster->getEquipped(new \MyGame\Sample\Weapon())->getName(); // "Axe" $weapon_damage = $monster->getEquipped(new \MyGame\Sample\Weapon())->getDamage(); // 5 }
</div>
<div class="language-c">
~~~{.c}
// Access union type field.
if (ns(Monster_equipped_type(monster)) == ns(Equipment_Weapon)) {
// Cast to appropriate type:
// C allows for silent void pointer assignment, so we need no explicit cast.
ns(Weapon_table_t) weapon = ns(Monster_equipped(monster));
const char *weapon_name = ns(Weapon_name(weapon)); // "Axe"
uint16_t weapon_damage = ns(Weapon_damage(weapon)); // 5
}
Mutating FlatBuffers
As you saw above, typically once you have created a FlatBuffer, it is read-only from that moment on. There are, however, cases where you have just received a FlatBuffer, and you'd like to modify something about it before sending it on to another recipient. With the above functionality, you'd have to generate an entirely new FlatBuffer, while tracking what you modified in your own data structures. This is inconvenient.
For this reason FlatBuffers can also be mutated in-place. While this is great for making small fixes to an existing buffer, you generally want to create buffers from scratch whenever possible, since it is much more efficient and the API is much more general purpose.
To get non-const accessors, invoke flatc
with --gen-mutable
.
Similar to how we read fields using the accessors above, we can now use the mutators like so:
We use the somewhat verbose term mutate
instead of set
to indicate that this
is a special use case, not to be confused with the default way of constructing
FlatBuffer data.
After the above mutations, you can send on the FlatBuffer to a new recipient without any further work!
Note that any mutate
functions on a table will return a boolean, which is
false
if the field we're trying to set is not present in the buffer. Fields
that are not present if they weren't set, or even if they happen to be equal to
the default value. For example, in the creation code above, the mana
field is equal to 150
, which is the default value, so it was never stored in
the buffer. Trying to call the corresponding mutate
method for mana
on such
data will return false
, and the value won't actually be modified!
One way to solve this is to call ForceDefaults
on a FlatBufferBuilder to
force all fields you set to actually be written. This, of course, increases the
size of the buffer somewhat, but this may be acceptable for a mutable buffer.
JSON with FlatBuffers
Using flatc
as a Conversion Tool
This is often the preferred method to use JSON with FlatBuffers, as it doesn't require you to add any new code to your program. It is also efficient, since you can ship with the binary data. The drawback is that it requires an extra step for your users/developers to perform (although it may be able to be automated as part of your compilation).
Lets say you have a JSON file that describes your monster. In this example,
we will use the file flatbuffers/samples/monsterdata.json
.
Here are the contents of the file:
{
pos: {
x: 1,
y: 2,
z: 3
},
hp: 300,
name: "Orc"
}
You can run this file through the flatc
compile with the -b
flag and
our monster.fbs
schema to produce a FlatBuffer binary file.
./../flatc -b monster.fbs monsterdata.json
The output of this will be a file monsterdata.bin
, which will contain the
FlatBuffer binary representation of the contents from our .json
file.
Advanced Features for Each Language
Each language has a dedicated Use in XXX
page in the Programmer's Guide
to cover the nuances of FlatBuffers in that language.
For your chosen language, see: