mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 04:04:19 +00:00
740 lines
24 KiB
Markdown
740 lines
24 KiB
Markdown
# Tutorial
|
|
|
|
This tutorial provides an example of how to work with FlatBuffers in a variety
|
|
of languages. The following topics will cover all the steps of using FlatBuffers
|
|
in your application.
|
|
|
|
1. Writing a FlatBuffers schema file (`.fbs`).
|
|
2. Using the `flatc` compiler to transform the schema into language-specific
|
|
code.
|
|
3. Importing the generated code and libraries into your application.
|
|
4. Serializing data into a flatbuffer.
|
|
5. Deserializing a flatbuffer.
|
|
|
|
The tutorial is structured to be language agnostic, with language specifics in
|
|
code blocks providing more context. Additionally, this tries to cover the major
|
|
parts and type system of flatbuffers to give a general overview. It's not
|
|
expected to be an exhaustive list of all features, or provide the best way to do
|
|
things.
|
|
|
|
## FlatBuffers Schema (`.fbs`)
|
|
|
|
To start working with FlatBuffers, you first need to create a
|
|
[schema](schema.md) file which defines the format of the data structures you
|
|
wish to serialize. The schema is processed by the `flatc` compiler to generate
|
|
language-specific code that you use in your projects.
|
|
|
|
The following
|
|
[`monster.fbs`](https://github.com/google/flatbuffers/blob/master/samples/monster.fbs)
|
|
schema will be used for this tutorial. This is part of the FlatBuffers
|
|
[sample code](https://github.com/google/flatbuffers/tree/master/samples) to give
|
|
complete sample binaries demonstrations.
|
|
|
|
FlatBuffers schema is a Interface Definition Language (IDL) that has a couple
|
|
data structures, see the [schema](schema.md) documentation for a detail
|
|
description. Use the inline code annotations to get a brief synopsis of each
|
|
part of the schema.
|
|
|
|
```c title="monster.fbs" linenums="1"
|
|
// Example IDL file for our monster's schema.
|
|
|
|
namespace MyGame.Sample; //(1)!
|
|
|
|
enum Color:byte { Red = 0, Green, Blue = 2 } //(2)!
|
|
|
|
// Optionally add more tables.
|
|
union Equipment { Weapon } //(3)!
|
|
|
|
struct Vec3 { //(4)!
|
|
x:float; //(5)!
|
|
y:float;
|
|
z:float;
|
|
}
|
|
|
|
table Monster { //(6)!
|
|
pos:Vec3; //(7)!
|
|
mana:short = 150; //(8)!
|
|
hp:short = 100;
|
|
name:string; //(9)!
|
|
friendly:bool = false (deprecated); //(10)!
|
|
inventory:[ubyte]; //(11)!
|
|
color:Color = Blue;
|
|
weapons:[Weapon]; //(12)!
|
|
equipped:Equipment; //(13)!
|
|
path:[Vec3];
|
|
}
|
|
|
|
table Weapon {
|
|
name:string;
|
|
damage:short;
|
|
}
|
|
|
|
root_type Monster; //(14)!
|
|
```
|
|
|
|
1. FlatBuffers has support for namespaces to place the generated code into.
|
|
There is mixed level of support for namespaces (some languages don't have
|
|
namespaces), but for the C family of languages, it is fully supported.
|
|
|
|
2. Enums definitions can be defined with the backing numerical type. Implicit
|
|
numbering is supported, so that `Green` would have a value of 1.
|
|
|
|
3. A union represents a single value from a set of possible values. Its
|
|
effectively an enum (to represent the type actually store) and a value,
|
|
combined into one. In this example, the union is not very useful, since it
|
|
only has a single type.
|
|
|
|
4. A struct is a collection of scalar fields with names. It is itself a scalar
|
|
type, which uses less memory and has faster lookup. However, once a struct is
|
|
defined, it cannot be changed. Use tables for data structures that can evolve
|
|
over time.
|
|
|
|
5. FlatBuffers has the standard set of scalar numerical types (`int8`, `int16`,
|
|
`int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`, `float`, `double`),
|
|
as well as `bool`. Note, scalars are fixed width, `varints` are not
|
|
supported.
|
|
|
|
6. Tables are the main data structure for grouping data together. It can evolve
|
|
by adding and deprecating fields over time, while preserving forward and
|
|
backwards compatibility.
|
|
|
|
7. A field that happens to be a `struct`. This means the data of the `Vec3`
|
|
struct will be serialized inline in the table without any need for offset.
|
|
|
|
8. Fields can be provided a default value. Default values can be configured to
|
|
not be serialized at all while still providing the default value while
|
|
deserializing. However, once set, a default value cannot be changed.
|
|
|
|
9. A `string` field which points to a serialized string external to the table.
|
|
|
|
10. A deprecated field that is no longer being used. This is used instead of
|
|
removing the field outright.
|
|
|
|
11. A `vector` field that points to a vector of bytes. Like `strings`, the
|
|
vector data is serialized elsewhere and this field just stores an offset to
|
|
the vector.
|
|
|
|
12. Vector of `tables` and `structs` are also possible.
|
|
|
|
13. A field to a `union` type.
|
|
|
|
14. The root of the flatbuffer is always a `table`. This indicates the type of
|
|
`table` the "entry" point of the flatbuffer will point to.
|
|
|
|
## Compiling Schema to Code (`flatc`)
|
|
|
|
After a schema file is written, you compile it to code in the languages you wish
|
|
to work with. This compilation is done by the [FlatBuffers Compiler](flatc.md)
|
|
(`flatc`) which is one of the binaries built in the repo.
|
|
|
|
### Building `flatc`
|
|
|
|
FlatBuffers uses [`cmake`](https://cmake.org/) to build projects files for your
|
|
environment.
|
|
|
|
=== "Unix"
|
|
|
|
```sh
|
|
cmake -G "Unix Makefiles"
|
|
make flatc
|
|
```
|
|
|
|
=== "Windows"
|
|
|
|
```sh
|
|
cmake -G "Visual Studio 17 2022"
|
|
msbuild.exe FlatBuffers.sln
|
|
```
|
|
|
|
See the documentation on [building](building.md) for more details and other
|
|
environments. Some languages also include a prebuilt `flatc` via their package
|
|
manager.
|
|
|
|
### Compiling Schema
|
|
|
|
To compile the schema, invoke `flatc` with the schema file and the language
|
|
flags you wish to generate code for. This compilation will generate files that
|
|
you include in your application code. These files provide convenient APIs for
|
|
serializing and deserializing the flatbuffer binary data.
|
|
|
|
=== "C++"
|
|
|
|
```sh
|
|
flatc --cpp monster.fbs
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```sh
|
|
flatc --csharp monster.fbs
|
|
```
|
|
|
|
You can deserialize flatbuffers in languages that differ from the language that
|
|
serialized it. For purpose of this tutorial, we assume one language is used for
|
|
both serializing and deserializing.
|
|
|
|
## Application Integration
|
|
|
|
The generated files are then included in your project to be built into your
|
|
application. This is heavily dependent on your build system and language, but
|
|
generally involves two things:
|
|
|
|
1. Importing the generated code.
|
|
2. Importing the "runtime" libraries.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
#include "monster_generated.h" // This was generated by `flatc`
|
|
#include "flatbuffers.h" // The runtime library for C++
|
|
|
|
// Simplifies naming in the following examples.
|
|
using namespace MyGame::Sample; // Specified in the schema.
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
using Google.FlatBuffers; // The runtime library for C#
|
|
using MyGame.Sample; // The generated files from `flatc`
|
|
```
|
|
|
|
For some languages the runtime libraries are just code files you compile into
|
|
your application. While other languages provide packaged libraries via their
|
|
package managers.
|
|
|
|
The generated files include APIs for both serializing and deserializing
|
|
FlatBuffers. So these steps are identical for both the consumer and producer.
|
|
|
|
## Serialization
|
|
|
|
Once all the files are included into your application, it's time to start
|
|
serializing some data!
|
|
|
|
With FlatBuffers, serialization can be a bit verbose, since each piece of data
|
|
must be serialized separately and in a particular order (depth-first, pre-order
|
|
traversal). The verbosity allows efficient serialization without heap
|
|
allocations, at the cost of more complex serialization APIs.
|
|
|
|
For example, any reference type (e.g. `table`, `vector`, `string`) must be
|
|
serialized before it can be referred to by other structures. So its typical to
|
|
serialize the data from leaf to root node, as will be shown below.
|
|
|
|
### FlatBufferBuilder
|
|
|
|
Most languages use a Builder object for managing the binary array that the data
|
|
is serialized into. It provides an API for serializing data, as well as keeps
|
|
track of some internal state. The generated code wraps methods on the Builder
|
|
object to provide an API tailored to the schema.
|
|
|
|
First instantiate a Builder (or reuse an existing one) and specify some memory
|
|
for it. The builder will automatically resize the backing buffer when necessary.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// Construct a Builder with 1024 byte backing array.
|
|
flatbuffers::FlatBufferBuidler builder(1024);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// Construct a Builder with 1024 byte backing array.
|
|
FlatBufferBuilder builder = new FlatBufferBuilder(1024);
|
|
```
|
|
|
|
Once a Builder is available, data can be serialized to it via the Builder APIs
|
|
and the generated code.
|
|
|
|
### Serializing Data
|
|
|
|
In this tutorial, we are building `Monsters` and `Weapons` for a computer game.
|
|
A `Weapon` is represented by a flatbuffer `table` with some fields. One field is
|
|
the `name` field, which is type `string`.
|
|
|
|
```c title="monster.fbs" linenums="28"
|
|
table Weapon {
|
|
name:string;
|
|
damage:short;
|
|
}
|
|
```
|
|
|
|
#### Strings
|
|
|
|
Since `string` is a reference type, we first need to serialize it before
|
|
assigning it to the `name` field of the `Weapon` table. This is done through the
|
|
Builder `CreateString` method:
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
flatbuffers::Offset<String> weapon_one_name = builder.CreateString("Sword");
|
|
flatbuffers::Offset<String> weapon_two_name = builder.CreateString("Axe");
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
Offset<String> weaponOneName = builder.CreateString("Sword");
|
|
Offset<String> weaponTwoName = builder.CreateString("Axe");
|
|
```
|
|
|
|
This performs the actual serialization (the string data is copied into the
|
|
backing array) and returns an offset. Think of the offset as a handle to that
|
|
reference. It's just a "typed" numerical offset to where that data resides in
|
|
the buffer.
|
|
|
|
#### Tables
|
|
|
|
Now that we have some names serialized, we can serialize `Weapons`. Here we will
|
|
use one of the generated helper functions that was emitted by `flatc`. The
|
|
`CreateWeapon` function takes in the Builder object, as well as the offset to
|
|
the weapon's name and a numerical value for the damage field.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
short weapon_one_damage = 3;
|
|
short weapon_two_damage = 5;
|
|
|
|
// Use the `CreateWeapon()` shortcut to create Weapons with all the fields set.
|
|
flatbuffers::Offset<Weapon> sword =
|
|
CreateWeapon(builder, weapon_one_name, weapon_one_damage);
|
|
flatbuffers::Offset<Weapon> axe =
|
|
CreateWeapon(builder, weapon_two_name, weapon_two_damage);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
short weaponOneDamage = 3;
|
|
short weaponTwoDamage = 5;
|
|
|
|
// Use the `CreateWeapon()` helper function to create the weapons, since we set every field.
|
|
Offset<Weapon> sword =
|
|
Weapon.CreateWeapon(builder, weaponOneName, weaponOneDamage);
|
|
Offset<Weapon> axe =
|
|
Weapon.CreateWeapon(builder, weaponTwoName, weaponTwoDamage);
|
|
```
|
|
|
|
The generated functions from `flatc`, like `CreateWeapon`, are just composed of
|
|
various Builder API methods. So its not required to use the generated code, but
|
|
it does make things much simpler and compact.
|
|
|
|
Just like the `CreateString` methods, the table serialization functions return
|
|
an offset to the location of the serialized `Weapon` table.
|
|
|
|
Now that we have some `Weapons` serialized, we can serialize a `Monster`.
|
|
Looking at the schema again, this table has a lot more fields of various types.
|
|
Some of these need to be serialized beforehand, for the same reason we
|
|
serialized the name string before the weapon table.
|
|
|
|
!!! note inline end
|
|
|
|
There is no prescribed ordering of which table fields must be serialized
|
|
first, you could serialize in any order you want. You can also not serialize
|
|
a field to provide a `null` value, this is done by using an 0 offset value.
|
|
|
|
```c title="monster.fbs" linenums="15"
|
|
table Monster {
|
|
pos:Vec3;
|
|
mana:short = 150;
|
|
hp:short = 100;
|
|
name:string;
|
|
friendly:bool = false (deprecated);
|
|
inventory:[ubyte];
|
|
color:Color = Blue;
|
|
weapons:[Weapon];
|
|
equipped:Equipment;
|
|
path:[Vec3];
|
|
}
|
|
```
|
|
|
|
#### Vectors
|
|
|
|
The `weapons` field is a `vector` of `Weapon` tables. We already have two
|
|
`Weapons` serialized, so we just need to serialize a `vector` of those offsets.
|
|
The Builder provides multiple ways to create `vectors`.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// Create a std::vector of the offsets we had previous made.
|
|
std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
|
|
weapons_vector.push_back(sword);
|
|
weapons_vector.push_back(axe);
|
|
|
|
// Then serialize that std::vector into the buffer and again get an Offset
|
|
// to that vector. Use `auto` here since the full type is long, and it just
|
|
// a "typed" number.
|
|
auto weapons = builder.CreateVector(weapons_vector);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// Create an array of the two weapon offsets.
|
|
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);
|
|
```
|
|
|
|
While we are at it, let us serialize the other two vector fields: the
|
|
`inventory` field is just a vector of scalars, and the `path` field is a vector
|
|
of structs (which are scalar data as well). So these vectors can be serialized a
|
|
bit more directly.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// Construct an array of two `Vec3` structs.
|
|
Vec3 points[] = { Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f) };
|
|
|
|
// Serialize it as a vector of structs.
|
|
flatbuffers::Offset<flatbuffers::Vector<Vec3>> path =
|
|
builder.CreateVectorOfStructs(points, 2);
|
|
|
|
// 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};
|
|
flatbuffers::Offset<flatbuffers::Vector<unsigned char>> inventory =
|
|
builder.CreateVector(treasure, 10);
|
|
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// Start building a path vector of length 2.
|
|
Monster.StartPathVector(fbb, 2);
|
|
|
|
// Serialize the individual Vec3 structs
|
|
Vec3.CreateVec3(builder, 1.0f, 2.0f, 3.0f);
|
|
Vec3.CreateVec3(builder, 4.0f, 5.0f, 6.0f);
|
|
|
|
// End the vector to get the offset
|
|
Offset<Vector<Vec3>> path = fbb.EndVector();
|
|
|
|
// 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);
|
|
}
|
|
Offset<Vector<byte>> inventory = builder.EndVector();
|
|
```
|
|
|
|
#### Unions
|
|
|
|
The last non-scalar data for the `Monster` table is the `equipped` `union`
|
|
field. For this case, we will reuse an already serialized `Weapon` (the only
|
|
type in the union), without needing to reserialize it. Union fields implicitly
|
|
add a hidden `_type` field that stores the type of value stored in the union.
|
|
When serializing a union, you must explicitly set this type field, along with
|
|
providing the union value.
|
|
|
|
We will also serialize the other scalar data at the same time, since we have all
|
|
the necessary values and Offsets to make a `Monster`.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// Create the remaining data needed for the Monster.
|
|
auto name = builder.CreateString("Orc");
|
|
|
|
// Create the position struct
|
|
auto position = Vec3(1.0f, 2.0f, 3.0f);
|
|
|
|
// Set his hit points to 300 and his mana to 150.
|
|
int hp = 300;
|
|
int mana = 150;
|
|
|
|
// Finally, create the monster using the `CreateMonster` helper function
|
|
// to set all fields.
|
|
//
|
|
// Here we set the union field by using the `.Union()` method of the
|
|
// `Offset<Weapon>` axe we already serialized above. We just have to specify
|
|
// which type of object we put in the union, and do that with the
|
|
// auto-generated `Equipment_Weapon` enum.
|
|
flatbuffers::Offset<Monster> orc =
|
|
CreateMonster(builder, &position, mana, hp, name, inventory,
|
|
Color_Red, weapons, Equipment_Weapon, axe.Union(),
|
|
path);
|
|
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// Create the remaining data needed for the Monster.
|
|
var name = builder.CreateString("Orc");
|
|
|
|
// Create our monster using `StartMonster()` and `EndMonster()`.
|
|
Monster.StartMonster(builder);
|
|
Monster.AddPos(builder, Vec3.CreateVec3(builder, 1.0f, 2.0f, 3.0f));
|
|
Monster.AddHp(builder, (short)300);
|
|
Monster.AddName(builder, name);
|
|
Monster.AddInventory(builder, inv);
|
|
Monster.AddColor(builder, Color.Red);
|
|
Monster.AddWeapons(builder, weapons);
|
|
// For union fields, we explicitly add the auto-generated enum for the type
|
|
// of value stored in the union.
|
|
Monster.AddEquippedType(builder, Equipment.Weapon);
|
|
// And we just use the `.Value` property of the already serialized axe.
|
|
Monster.AddEquipped(builder, axe.Value); // Axe
|
|
Monster.AddPath(builder, path);
|
|
Offset<Monster> orc = Monster.EndMonster(builder);
|
|
```
|
|
|
|
### Finishing
|
|
|
|
At this point, we have serialized a `Monster` we've named "orc" to the
|
|
flatbuffer and have its offset. The `root_type` of the schema is also a
|
|
`Monster`, so we have everything we need to finish the serialization step.
|
|
|
|
This is done by calling the appropriate `finish` method on the Builder, passing
|
|
in the orc offset to indicate this `table` is the "entry" point when
|
|
deserializing the buffer later.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// Call `Finish()` to instruct the builder that this monster is complete.
|
|
// You could also call `FinishMonsterBuffer(builder, orc);`
|
|
builder.Finish(orc);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// Call `Finish()` to instruct the builder that this monster is complete.
|
|
// You could also call `Monster.FinishMonsterBuffer(builder, orc);`
|
|
builder.Finish(orc.Value);
|
|
```
|
|
|
|
Once you finish a Builder, you can no longer serialize more data to it.
|
|
|
|
#### Buffer Access
|
|
|
|
The flatbuffer is now ready to be stored somewhere, sent over the network,
|
|
compressed, or whatever you would like to do with it. You access the raw buffer
|
|
like so:
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
// This must be called after `Finish()`.
|
|
uint8_t *buf = builder.GetBufferPointer();
|
|
|
|
// Returns the size of the buffer that `GetBufferPointer()` points to.
|
|
int size = builder.GetSize();
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// This must be called after `Finish()`.
|
|
//
|
|
// The data in this ByteBuffer does NOT start at 0, but at buf.Position.
|
|
// The end of the data is marked by buf.Length, so the size is
|
|
// buf.Length - buf.Position.
|
|
FlatBuffers.ByteBuffer dataBuffer = builder.DataBuffer;
|
|
|
|
// Alternatively this copies the above data out of the ByteBuffer for you:
|
|
byte[] buf = builder.SizedByteArray();
|
|
```
|
|
|
|
Now you can write the bytes to a file or send them over the network. The buffer
|
|
stays valid until the Builder is cleared or destroyed.
|
|
|
|
Make sure your file mode (or transfer protocol) is set to BINARY, and not TEXT.
|
|
If you try to transfer a flatbuffer in TEXT mode, the buffer will be corrupted
|
|
and be hard to diagnose.
|
|
|
|
## Deserialization
|
|
|
|
Deserialization is a bit of a misnomer, since FlatBuffers doesn't deserialize
|
|
the whole buffer when accessed. It just "decodes" the data that is requested,
|
|
leaving all the other data untouched. It is up to the application to decide if
|
|
the data is copied out or even read in the first place. However, we continue to
|
|
use the word `deserialize` to mean accessing data from a binary flatbuffer.
|
|
|
|
Now that we have successfully create an orc FlatBuffer, the data can be saved,
|
|
sent over a network, etc. At some point, the buffer will be accessed to obtain
|
|
the underlying data.
|
|
|
|
The same application setup used for serialization is needed for deserialization
|
|
(see [application integration](#application-integration)).
|
|
|
|
### Root Access
|
|
|
|
All access to the data in the flatbuffer must first go through the root object.
|
|
There is only one root object per flatbuffer. The generated code provides
|
|
functions to get the root object given the buffer.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
uint8_t *buffer_pointer = /* the data you just read */;
|
|
|
|
// Get an view to the root object inside the buffer.
|
|
Monster monster = GetMonster(buffer_pointer);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
byte[] bytes = /* the data you just read */
|
|
|
|
// Get an view to the root object inside the buffer.
|
|
Monster monster = Monster.GetRootAsMonster(new ByteBuffer(bytes));
|
|
```
|
|
|
|
Again, make sure you read the bytes in BINARY mode, otherwise the buffer may be
|
|
corrupted.
|
|
|
|
In most languages, the returned object is just a "view" of the data with helpful
|
|
accessors. Data is typically not copied out of the backing buffer. This also
|
|
means the backing buffer must remain alive for the duration of the views.
|
|
|
|
### Table Access
|
|
|
|
If you look in the generated files emitted by `flatc`, you will see it generated
|
|
, for each `table`, accessors of all its non-`deprecated` fields. For example,
|
|
some of the accessors of the `Monster` root table would look like:
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
auto hp = monster->hp();
|
|
auto mana = monster->mana();
|
|
auto name = monster->name()->c_str();
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
// For C#, unlike most other languages support by FlatBuffers, most values
|
|
// (except for vectors and unions) are available as properties instead of
|
|
// accessor methods.
|
|
var hp = monster.Hp;
|
|
var mana = monster.Mana;
|
|
var name = monster.Name;
|
|
```
|
|
|
|
These accessors should hold the values `300`, `150`, and `"Orc"` respectively.
|
|
|
|
The default value of `150` wasn't stored in the `mana` field, but we are still
|
|
able to retrieve it. That is because the generated accessors return a hard-coded
|
|
default value when it doesn't find the value in the buffer.
|
|
|
|
#### Nested Object Access
|
|
|
|
Accessing nested objects is very similar, with the nested field pointing to
|
|
another object type. Be careful, the field could be `null` if not present.
|
|
|
|
For example, accessing the `pos` `struct`, which is type `Vec3` you would do:
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
auto pos = monster->pos();
|
|
auto x = pos->x();
|
|
auto y = pos->y();
|
|
auto z = pos->z();
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
var pos = monster.Pos.Value;
|
|
var x = pos.X;
|
|
var y = pos.Y;
|
|
var z = pos.Z;
|
|
```
|
|
|
|
Where `x`, `y`, and `z` will contain `1.0`, `2.0`, and `3.0` respectively.
|
|
|
|
### Vector Access
|
|
|
|
Similarly, we can access elements of the `inventory` `vector` by indexing it.
|
|
You can also iterate over the length of the vector.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
flatbuffers::Vector<unsigned char> inv = monster->inventory();
|
|
auto inv_len = inv->size();
|
|
auto third_item = inv->Get(2);
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
int invLength = monster.InventoryLength;
|
|
var thirdItem = monster.Inventory(2);
|
|
```
|
|
|
|
For vectors of tables, you can access the elements like any other vector, except
|
|
you need to handle the result as a FlatBuffer table. Here we iterate over the
|
|
`weapons` vector that is houses `Weapon` `tables`.
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
flatbuffers::Vector<Weapon> weapons = monster->weapons();
|
|
auto weapon_len = weapons->size();
|
|
auto second_weapon_name = weapons->Get(1)->name()->str();
|
|
auto second_weapon_damage = weapons->Get(1)->damage()
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
int weaponsLength = monster.WeaponsLength;
|
|
var secondWeaponName = monster.Weapons(1).Name;
|
|
var secondWeaponDamage = monster.Weapons(1).Damage;
|
|
```
|
|
|
|
### Union Access
|
|
|
|
Lastly , we can access our `equipped` `union` field. 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`).
|
|
|
|
=== "C++"
|
|
|
|
```c++
|
|
auto union_type = monster.equipped_type();
|
|
|
|
if (union_type == Equipment_Weapon) {
|
|
// Requires `static_cast` to type `const Weapon*`.
|
|
auto weapon = static_cast<const Weapon*>(monster->equipped());
|
|
|
|
auto weapon_name = weapon->name()->str(); // "Axe"
|
|
auto weapon_damage = weapon->damage(); // 5
|
|
}
|
|
```
|
|
|
|
=== "C#"
|
|
|
|
```c#
|
|
var unionType = monster.EquippedType;
|
|
|
|
if (unionType == Equipment.Weapon) {
|
|
var weapon = monster.Equipped<Weapon>().Value;
|
|
|
|
var weaponName = weapon.Name; // "Axe"
|
|
var weaponDamage = weapon.Damage; // 5
|
|
}
|
|
```
|