Using Flatbuffers
Note that the Flatbuffers tutorials are phenomenal. This guide doesn't aim to replace them, but to supplement.
Installing Flatbuffers (Linux)
# Select a folder place the source code (e.g. ~/dev/third-party/)
mkdir -p ~/dev/third-party && cd ~/dev/third-party
# Clone the repository
git clone https://github.com/google/flatbuffers
cd flatbuffers
# Generate makefiles, build, and install
cmake -G "Unix Makefiles"
make
sudo make install
# Select a folder place the source code (e.g. ~/dev/third-party/)
mkdir -p ~/dev/third-party && cd ~/dev/third-party
# Clone the repository
git clone https://github.com/google/flatbuffers
cd flatbuffers
# Generate makefiles, build, and install
cmake -G "Unix Makefiles"
make
sudo make install
Schemas
The work with flatbuffers you need to define a schema
. This file tells flatbuffers what each data structure consists of and how it's formatted. Schemas are written in the Interface Definition Language (IDL), which uses syntax similar to C style languages.
From the flatbuffer tutorial, here is an example schema:
// Defines the namespace the generated code will be given
namespace MyGame.Sample;
// An enum that can be used in the schema is declared like this
enum Color:byte { Red = 0, Green, Blue = 2 }
// You can couple tables using the union keyword
union Equipment { Weapon, Backpack } // Optionally add more tables.
// Structs can also be used in the schema like this
struct Vec3 {
x:float;
y:float;
z:float;
}
// This is the main object in the flatbuffer. It uses all
// the other components (enums, structs, tables) defined
// elsewhere in the schema
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.
path:[Vec3]; // Vector of structs.
}
table Weapon {
name:string;
damage:short;
}
table Backpack {
capacity:short;
}
// The root type declares the root table for the serialized data
root_type Monster;
// Defines the namespace the generated code will be given
namespace MyGame.Sample;
// An enum that can be used in the schema is declared like this
enum Color:byte { Red = 0, Green, Blue = 2 }
// You can couple tables using the union keyword
union Equipment { Weapon, Backpack } // Optionally add more tables.
// Structs can also be used in the schema like this
struct Vec3 {
x:float;
y:float;
z:float;
}
// This is the main object in the flatbuffer. It uses all
// the other components (enums, structs, tables) defined
// elsewhere in the schema
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.
path:[Vec3]; // Vector of structs.
}
table Weapon {
name:string;
damage:short;
}
table Backpack {
capacity:short;
}
// The root type declares the root table for the serialized data
root_type Monster;
Compiling the Schema
Once you've defined your schema you have to compile with with flatc
. If the above schema was called monster.fbs
, this will generate a file called monster_generated.h
(for C++) which should be included in whatever file you expect to conduct the serialization in.
Reading and Writing Flatbuffers (in process of deprecating)
First, create an instance of the FlatBufferBuilder
which will contain the buffer as items are added to it.
flatbuffers::FlatBufferBuilder builder(1024);
flatbuffers::FlatBufferBuilder builder(1024);
Using the builder
you can use the CreateX
shortcut (where X
is the name of the table) to build whole tables quickly. Any objects that are contained within the root table have to be serialized before they're added.
Optionally, you can add each element to the flatbuffer manually by creating a builder object for the specific table and then adding elements one-by-one
MonsterBuilder monster_builder(builder);
monster_builder.add_pos(&position);
monster_builder.add_hp(hp);
...
monster_builder.add_equipped_type(Equipment_Weapon);
monster_builder.add_equipped(axe.Union());
auto mon = monster_builder.Finish();
MonsterBuilder monster_builder(builder);
monster_builder.add_pos(&position);
monster_builder.add_hp(hp);
...
monster_builder.add_equipped_type(Equipment_Weapon);
monster_builder.add_equipped(axe.Union());
auto mon = monster_builder.Finish();
Writing Flatbuffers
- Create an instance of
FlatBufferBuilder
which will contain the buffer as items are added to it.
// Create a flatbuffer builder
flatbuffers::FlatBufferBuidler fbb;
// Create a flatbuffer builder
flatbuffers::FlatBufferBuidler fbb;
- Using the
builder
you can use theCreateX
shortcut to build whole tables quickly (whereX
is the name of the table). Any objects contained within the root table have to be serialized before they're added. Alternatively, you can add each element to the manually by creating a builder object for the specific table and then adding elements one-by-one.
MonsterBuilder mon_builder(fbb);
mon_builder.add_pos(&position);
mon_builder.add_hp(hp);
...
auto monster = mon_builder.Finish(); // Apply to current offest
fbb.Finish(monster); // Apply the final offset
MonsterBuilder mon_builder(fbb);
mon_builder.add_pos(&position);
mon_builder.add_hp(hp);
...
auto monster = mon_builder.Finish(); // Apply to current offest
fbb.Finish(monster); // Apply the final offset
Writing Unions
Unions are useful for collection tables or structs that are logically associated, but may differ depending on the data being sent. To write a union, use either of methods in (2), and additionally specify the _type
.
mon_builder.add_equipped_type(Equipment_Weapon);
mon_builder.add_equipped(axe.Union()); // You must call .Union()
mon_builder.add_equipped_type(Equipment_Weapon);
mon_builder.add_equipped(axe.Union()); // You must call .Union()
Object API
To initialize the object API, add --gen-object-api
as a flag to the Flatbuffer compiler, e.g., flatc
. More information can be found in the FlatBuffers: Use in C++ guide.
Writing Flatbuffers with the Object API
// Instantiate a struct with the xT type
::request::RequestT req_t;
// Populate the struct members directly
req_t.payload = payload;
req_t.payload_len = req_payload_size;
req_t.command_id = fb::camera::request::REQ_CMD_STREAM_START;
// Pack it with a builder
flatbuffers::FlatBufferBuilder fbbr;
fbbr.Finish(request::Request::Pack(fbbr, &req_t));
// Use the buffer pointer and size as usual
uint8_t *req_buf = fbbr.GetBufferPointer();
int req_size = fbbr.GetSize();
// Instantiate a struct with the xT type
::request::RequestT req_t;
// Populate the struct members directly
req_t.payload = payload;
req_t.payload_len = req_payload_size;
req_t.command_id = fb::camera::request::REQ_CMD_STREAM_START;
// Pack it with a builder
flatbuffers::FlatBufferBuilder fbbr;
fbbr.Finish(request::Request::Pack(fbbr, &req_t));
// Use the buffer pointer and size as usual
uint8_t *req_buf = fbbr.GetBufferPointer();
int req_size = fbbr.GetSize();
Reading Flatbuffers with the Object API
// Instantiate a struct with the xT type
CameraRequestT cam_req;
// Unpack the data
GetCameraRequest(req.data())->UnPackTo(&cam_req);
// Access the members directly
cam_req.system_id
// Instantiate a struct with the xT type
CameraRequestT cam_req;
// Unpack the data
GetCameraRequest(req.data())->UnPackTo(&cam_req);
// Access the members directly
cam_req.system_id
Writing Unions with the Object API
// This example uses a union of tables,
// first, create the table that goes in the union.
fb::camera::request::StreamOptionsT sopt_t;
sopt_t.stream_target = fb::camera::request::STREAM_TARGET_UDP;
sopt_t.udp_host = "192.168.245.123";
sopt_t.udp_port = 19200;
flatbuffers::FlatBufferBuilder fbbs;
fbbs.Finish(fb::camera::request::StreamOptions::Pack(fbbs, &sopt_t));
// Next, create the union object by specifying the type,
// and using the .Set method on the union.
fb::camera::request::OptionsUnion opt_union;
opt_union.type = fb::camera::request::Options_StreamOptions;
opt_union.Set(std::move(sopt_t));
// Add the union to the base table.
fb::camera::request::CameraRequestT cam_req_t;
cam_req_t.camera_name = camera_name;
cam_req_t.options = opt_union;
flatbuffers::FlatBufferBuilder fbbt;
fbbt.Finish(fb::camera::request::CameraRequest::Pack(fbbt, &cam_req_t));
// This example uses a union of tables,
// first, create the table that goes in the union.
fb::camera::request::StreamOptionsT sopt_t;
sopt_t.stream_target = fb::camera::request::STREAM_TARGET_UDP;
sopt_t.udp_host = "192.168.245.123";
sopt_t.udp_port = 19200;
flatbuffers::FlatBufferBuilder fbbs;
fbbs.Finish(fb::camera::request::StreamOptions::Pack(fbbs, &sopt_t));
// Next, create the union object by specifying the type,
// and using the .Set method on the union.
fb::camera::request::OptionsUnion opt_union;
opt_union.type = fb::camera::request::Options_StreamOptions;
opt_union.Set(std::move(sopt_t));
// Add the union to the base table.
fb::camera::request::CameraRequestT cam_req_t;
cam_req_t.camera_name = camera_name;
cam_req_t.options = opt_union;
flatbuffers::FlatBufferBuilder fbbt;
fbbt.Finish(fb::camera::request::CameraRequest::Pack(fbbt, &cam_req_t));
Reading Unions with the Object API
// First, ensure that the type is what you intend
assert(cam_req.options.type == fb::camera::request::Options_StreamOptions);
// Next, cast the data to a variable
auto stream_opts = cam_req.options.AsStreamOptions();
// First, ensure that the type is what you intend
assert(cam_req.options.type == fb::camera::request::Options_StreamOptions);
// Next, cast the data to a variable
auto stream_opts = cam_req.options.AsStreamOptions();
Troubleshooting
A runtime errors similar to below
void flatbuffers::FlatBufferBuilder::NotNested(): Assertion `!nested' failed.
void flatbuffers::FlatBufferBuilder::NotNested(): Assertion `!nested' failed.
means that you are creating an offset item within the scope of the builder you're adding it too; flatbuffers doesn't allow this (for some reason). Move any calls to fbb.CreateString
or similar outside of the inner builder, e.g., mon_builder
or CreateX
command.
ADDITIONAL RESOURCES
Gen-Object API