// Example on how to build a Monster FlatBuffer. // Note: while some older C89 compilers are supported when // -DFLATCC_PORTABLE is defined, this particular sample is known not to // not work with MSVC 2010 (MSVC 2013 is OK) due to inline variable // declarations. These are easily move to the start of code blocks, but // since we follow the step-wise tutorial, it isn't really practical // in this case. The comment style is technically also in violation of C89. #include "monster_builder.h" // Generated by `flatcc`. // and already included. // Convenient namespace macro to manage long namespace prefix. // The ns macro makes it possible to write `ns(Monster_create(...))` // instead of `MyGame_Sample_Monster_create(...)` #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])) // This allows us to verify result in optimized builds. #define test_assert(x) do { if (!(x)) { assert(0); return -1; }} while(0) // Bottom-up approach where we create child objects and store these // in temporary references before a parent object is created with // these references. int create_monster_bottom_up(flatcc_builder_t *B, int flexible) { flatbuffers_string_ref_t weapon_one_name = flatbuffers_string_create_str(B, "Sword"); int16_t weapon_one_damage = 3; flatbuffers_string_ref_t weapon_two_name = flatbuffers_string_create_str(B, "Axe"); int16_t weapon_two_damage = 5; // Use the `MyGame_Sample_Weapon_create` shortcut to create Weapons // with all the fields set. // // In the C-API, verbs (here create) always follow the type name // (here Weapon), prefixed by the namespace (here MyGame_Sample_): // MyGame_Sample_Weapon_create(...), or ns(Weapone_create(...)). 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)); // 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)); // Here we use a top-down approach locally to build a Weapons vector // in-place instead of creating a temporary external vector to use // as argument like we did with the `inventory` earlier on, but the // overall approach is still bottom-up. ns(Weapon_vec_start(B)); ns(Weapon_vec_push(B, sword)); ns(Weapon_vec_push(B, axe)); ns(Weapon_vec_ref_t) weapons = ns(Weapon_vec_end(B)); // Create a `Vec3`, representing the Orc's position in 3-D space. ns(Vec3_t) pos = { 1.0f, 2.0f, 3.0f }; // Set his hit points to 300 and his mana to 150. int16_t hp = 300; // The default value is 150, so we will never store this field. int16_t mana = 150; // Create the equipment union. In the C++ language API this is given // as two arguments to the create call, or as two separate add // operations for the type and the table reference. Here we create // a single union value that carries both the type and reference. ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); if (!flexible) { // Finally, create the monster using the `Monster_create` helper function // to set all fields. // // Note that the Equipment union only take up one argument in C, where // C++ takes a type and an object argument. ns(Monster_create_as_root(B, &pos, mana, hp, name, inventory, ns(Color_Red), weapons, equipped)); // Unlike C++ we do not use a Finish call. Instead we use the // `create_as_root` action which has better type safety and // simplicity. // // However, we can also express this as: // // ns(Monster_ref_t) orc = ns(Monster_create(B, ...)); // flatcc_builder_buffer_create(orc); // // In this approach the function should return the orc and // let a calling function handle the flatcc_buffer_create call // for a more composable setup that is also able to create child // monsters. In general, `flatcc_builder` calls are best isolated // in a containing driver function. } else { // A more flexible approach where we mix bottom-up and top-down // style. We still create child objects first, but then create // a top-down style monster object that we can manipulate in more // detail. // 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)); // We could also drop the `as_root` suffix from Monster_start/end(B) // and add the table as buffer root later: // // ns(Monster_ref_t) orc = ns(Monster_start(B)); // ... // ns(Monster_ref_t) orc = ns(Monster_end(B)); // flatcc_builder_buffer_create(orc); // // It is best to keep the `flatcc_builder` calls in a containing // driver function for modularity. } return 0; } // Alternative top-down approach where parent objects are created before // their children. We only need to save one reference because the `axe` // object is used in two places effectively making the buffer object // graph a DAG. int create_monster_top_down(flatcc_builder_t *B) { uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; size_t treasure_count = c_vec_len(treasure); ns(Weapon_ref_t) axe; // NOTE: if we use end_as_root, we MUST also start as root. ns(Monster_start_as_root(B)); ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); ns(Monster_hp_add(B, 300)); //ns(Monster_mana_add(B, 150)); // We use create_str instead of add because we have no existing string reference. ns(Monster_name_create_str(B, "Orc")); // Again we use create because we no existing vector object, only a C-array. ns(Monster_inventory_create(B, treasure, treasure_count)); ns(Monster_color_add(B, ns(Color_Red))); if (1) { ns(Monster_weapons_start(B)); ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Sword"), 3)); // We reuse the axe object later. Note that we dereference a pointer // because push always returns a short-term pointer to the stored element. // We could also have created the axe object first and simply pushed it. axe = *ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Axe"), 5)); ns(Monster_weapons_end(B)); } else { // We can have more control with the table elements added to a vector: // ns(Monster_weapons_start(B)); ns(Monster_weapons_push_start(B)); ns(Weapon_name_create_str(B, "Sword")); ns(Weapon_damage_add(B, 3)); ns(Monster_weapons_push_end(B)); ns(Monster_weapons_push_start(B)); ns(Weapon_name_create_str(B, "Axe")); ns(Weapon_damage_add(B, 5)); axe = *ns(Monster_weapons_push_end(B)); ns(Monster_weapons_end(B)); } // Unions can get their type by using a type-specific add/create/start method. ns(Monster_equipped_Weapon_add(B, axe)); ns(Monster_end_as_root(B)); return 0; } // This isn't strictly needed because the builder already included the reader, // but we would need it if our reader were in a separate file. #include "monster_reader.h" #undef ns #define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. int access_monster_buffer(const void *buffer) { // 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. // Make sure the buffer is accessible. test_assert(monster != 0); int16_t hp = ns(Monster_hp(monster)); int16_t mana = ns(Monster_mana(monster)); // This is just a const char *, but it also supports a fast length operation. flatbuffers_string_t name = ns(Monster_name(monster)); size_t name_len = flatbuffers_string_len(name); test_assert(hp == 300); // Since 150 is the default, we are reading a value that wasn't stored. test_assert(mana == 150); test_assert(0 == strcmp(name, "Orc")); test_assert(name_len == strlen("Orc")); int hp_present = ns(Monster_hp_is_present(monster)); // 1 int mana_present = ns(Monster_mana_is_present(monster)); // 0 test_assert(hp_present); test_assert(!mana_present); ns(Vec3_struct_t) pos = ns(Monster_pos(monster)); // Make sure pos has been set. test_assert(pos != 0); float x = ns(Vec3_x(pos)); float y = ns(Vec3_y(pos)); float z = ns(Vec3_z(pos)); // The literal `f` suffix is important because double literals does // not always map cleanly to 32-bit represention even with only a few digits: // `1.0 == 1.0f`, but `3.2 != 3.2f`. test_assert(x == 1.0f); test_assert(y == 2.0f); test_assert(z == 3.0f); // We can also read the position into a C-struct. We have to copy // because we generally do not know if the native endian format // matches the one stored in the buffer (pe: protocol endian). ns(Vec3_t) pos_vec; // `pe` indicates endian conversion from protocol to native. ns(Vec3_copy_from_pe(&pos_vec, pos)); test_assert(pos_vec.x == 1.0f); test_assert(pos_vec.y == 2.0f); test_assert(pos_vec.z == 3.0f); // This is a const uint8_t *, but it shouldn't be accessed directly // to ensure proper endian conversion. However, uint8 (ubyte) are // not sensitive endianness, so we *could* have accessed it directly. // The compiler likely optimizes this so that it doesn't matter. flatbuffers_uint8_vec_t inv = ns(Monster_inventory(monster)); size_t inv_len = flatbuffers_uint8_vec_len(inv); // Make sure the inventory has been set. test_assert(inv != 0); // If `inv` were absent, the length would 0, so the above test is redundant. test_assert(inv_len == 10); // Index 0 is the first, index 2 is the third. // NOTE: C++ uses the `Get` terminology for vector elemetns, C use `at`. uint8_t third_item = flatbuffers_uint8_vec_at(inv, 2); test_assert(third_item == 2); ns(Weapon_vec_t) weapons = ns(Monster_weapons(monster)); size_t weapons_len = ns(Weapon_vec_len(weapons)); test_assert(weapons_len == 2); // We can use `const char *` instead of `flatbuffers_string_t`. const char *second_weapon_name = ns(Weapon_name(ns(Weapon_vec_at(weapons, 1)))); int16_t second_weapon_damage = ns(Weapon_damage(ns(Weapon_vec_at(weapons, 1)))); test_assert(second_weapon_name != 0 && strcmp(second_weapon_name, "Axe") == 0); test_assert(second_weapon_damage == 5); // Access union type field. if (ns(Monster_equipped_type(monster)) == ns(Equipment_Weapon)) { // Cast to appropriate type: // C does not require the cast to Weapon_table_t, but C++ does. ns(Weapon_table_t) weapon = (ns(Weapon_table_t)) ns(Monster_equipped(monster)); const char *weapon_name = ns(Weapon_name(weapon)); int16_t weapon_damage = ns(Weapon_damage(weapon)); test_assert(0 == strcmp(weapon_name, "Axe")); test_assert(weapon_damage == 5); } return 0; } #include int main(int argc, char *argv[]) { // Create a `FlatBufferBuilder`, which will be used to create our // monsters' FlatBuffers. flatcc_builder_t builder; void *buf; size_t size; // Silence warnings. (void)argc; (void)argv; // Initialize the builder object. flatcc_builder_init(&builder); test_assert(0 == create_monster_bottom_up(&builder, 0)); // Allocate and extract a readable buffer from internal builder heap. // NOTE: Finalizing the buffer does NOT change the builder, it // just creates a snapshot of the builder content. // NOTE2: finalize_buffer uses malloc while finalize_aligned_buffer // uses a portable aligned allocation method. Often the malloc // version is sufficient, but won't work for all schema on all // systems. If the buffer is written to disk or network, but not // accessed in memory, `finalize_buffer` is also sufficient. // The flatcc_builder version of free or aligned_free should be used // instead of `free` although free will often work on POSIX systems. // This ensures portability and prevents issues when linking to // allocation libraries other than malloc. buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); //buf = flatcc_builder_finalize_buffer(&builder, &size); // We now have a FlatBuffer we can store on disk or send over a network. // ** file/network code goes here :) ** // Instead, we're going to access it right away (as if we just received it). //access_monster_buffer(buf); // prior to v0.5.0, use `aligned_free` flatcc_builder_aligned_free(buf); //free(buf); // // The builder object can optionally be reused after a reset which // is faster than creating a new builder. Subsequent use might // entirely avoid temporary allocations until finalizing the buffer. flatcc_builder_reset(&builder); test_assert(0 == create_monster_bottom_up(&builder, 1)); buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); access_monster_buffer(buf); flatcc_builder_aligned_free(buf); flatcc_builder_reset(&builder); create_monster_top_down(&builder); buf = flatcc_builder_finalize_buffer(&builder, &size); test_assert(0 == access_monster_buffer(buf)); flatcc_builder_free(buf); // Eventually the builder must be cleaned up: flatcc_builder_clear(&builder); printf("The FlatBuffer was successfully created and accessed!\n"); return 0; }