monster.c (15096B)
1 // Example on how to build a Monster FlatBuffer. 2 3 4 // Note: while some older C89 compilers are supported when 5 // -DFLATCC_PORTABLE is defined, this particular sample is known not to 6 // not work with MSVC 2010 (MSVC 2013 is OK) due to inline variable 7 // declarations. These are easily move to the start of code blocks, but 8 // since we follow the step-wise tutorial, it isn't really practical 9 // in this case. The comment style is technically also in violation of C89. 10 11 12 #include "monster_builder.h" // Generated by `flatcc`. 13 // <string.h> and <assert.h> already included. 14 15 // Convenient namespace macro to manage long namespace prefix. 16 // The ns macro makes it possible to write `ns(Monster_create(...))` 17 // instead of `MyGame_Sample_Monster_create(...)` 18 #undef ns 19 #define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. 20 21 // A helper to simplify creating vectors from C-arrays. 22 #define c_vec_len(V) (sizeof(V)/sizeof((V)[0])) 23 24 // This allows us to verify result in optimized builds. 25 #define test_assert(x) do { if (!(x)) { assert(0); return -1; }} while(0) 26 27 // Bottom-up approach where we create child objects and store these 28 // in temporary references before a parent object is created with 29 // these references. 30 int create_monster_bottom_up(flatcc_builder_t *B, int flexible) 31 { 32 flatbuffers_string_ref_t weapon_one_name = flatbuffers_string_create_str(B, "Sword"); 33 int16_t weapon_one_damage = 3; 34 35 flatbuffers_string_ref_t weapon_two_name = flatbuffers_string_create_str(B, "Axe"); 36 int16_t weapon_two_damage = 5; 37 38 // Use the `MyGame_Sample_Weapon_create` shortcut to create Weapons 39 // with all the fields set. 40 // 41 // In the C-API, verbs (here create) always follow the type name 42 // (here Weapon), prefixed by the namespace (here MyGame_Sample_): 43 // MyGame_Sample_Weapon_create(...), or ns(Weapone_create(...)). 44 ns(Weapon_ref_t) sword = ns(Weapon_create(B, weapon_one_name, weapon_one_damage)); 45 ns(Weapon_ref_t) axe = ns(Weapon_create(B, weapon_two_name, weapon_two_damage)); 46 47 // Serialize a name for our monster, called "Orc". 48 // The _str suffix indicates the source is an ascii-z string. 49 flatbuffers_string_ref_t name = flatbuffers_string_create_str(B, "Orc"); 50 51 // Create a `vector` representing the inventory of the Orc. Each number 52 // could correspond to an item that can be claimed after he is slain. 53 uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 54 flatbuffers_uint8_vec_ref_t inventory; 55 // `c_vec_len` is the convenience macro we defined earlier. 56 inventory = flatbuffers_uint8_vec_create(B, treasure, c_vec_len(treasure)); 57 58 // Here we use a top-down approach locally to build a Weapons vector 59 // in-place instead of creating a temporary external vector to use 60 // as argument like we did with the `inventory` earlier on, but the 61 // overall approach is still bottom-up. 62 ns(Weapon_vec_start(B)); 63 ns(Weapon_vec_push(B, sword)); 64 ns(Weapon_vec_push(B, axe)); 65 ns(Weapon_vec_ref_t) weapons = ns(Weapon_vec_end(B)); 66 67 68 // Create a `Vec3`, representing the Orc's position in 3-D space. 69 ns(Vec3_t) pos = { 1.0f, 2.0f, 3.0f }; 70 71 72 // Set his hit points to 300 and his mana to 150. 73 int16_t hp = 300; 74 // The default value is 150, so we will never store this field. 75 int16_t mana = 150; 76 77 // Create the equipment union. In the C++ language API this is given 78 // as two arguments to the create call, or as two separate add 79 // operations for the type and the table reference. Here we create 80 // a single union value that carries both the type and reference. 81 ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); 82 83 if (!flexible) { 84 // Finally, create the monster using the `Monster_create` helper function 85 // to set all fields. 86 // 87 // Note that the Equipment union only take up one argument in C, where 88 // C++ takes a type and an object argument. 89 ns(Monster_create_as_root(B, &pos, mana, hp, name, inventory, ns(Color_Red), 90 weapons, equipped)); 91 92 // Unlike C++ we do not use a Finish call. Instead we use the 93 // `create_as_root` action which has better type safety and 94 // simplicity. 95 // 96 // However, we can also express this as: 97 // 98 // ns(Monster_ref_t) orc = ns(Monster_create(B, ...)); 99 // flatcc_builder_buffer_create(orc); 100 // 101 // In this approach the function should return the orc and 102 // let a calling function handle the flatcc_buffer_create call 103 // for a more composable setup that is also able to create child 104 // monsters. In general, `flatcc_builder` calls are best isolated 105 // in a containing driver function. 106 107 } else { 108 109 // A more flexible approach where we mix bottom-up and top-down 110 // style. We still create child objects first, but then create 111 // a top-down style monster object that we can manipulate in more 112 // detail. 113 114 // It is important to pair `start_as_root` with `end_as_root`. 115 ns(Monster_start_as_root(B)); 116 ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); 117 // or alternatively 118 //ns(Monster_pos_add(&pos); 119 120 ns(Monster_hp_add(B, hp)); 121 // Notice that `Monser_name_add` adds a string reference unlike the 122 // add_str and add_strn variants. 123 ns(Monster_name_add(B, name)); 124 ns(Monster_inventory_add(B, inventory)); 125 ns(Monster_color_add(B, ns(Color_Red))); 126 ns(Monster_weapons_add(B, weapons)); 127 ns(Monster_equipped_add(B, equipped)); 128 // Complete the monster object and make it the buffer root object. 129 ns(Monster_end_as_root(B)); 130 131 // We could also drop the `as_root` suffix from Monster_start/end(B) 132 // and add the table as buffer root later: 133 // 134 // ns(Monster_ref_t) orc = ns(Monster_start(B)); 135 // ... 136 // ns(Monster_ref_t) orc = ns(Monster_end(B)); 137 // flatcc_builder_buffer_create(orc); 138 // 139 // It is best to keep the `flatcc_builder` calls in a containing 140 // driver function for modularity. 141 } 142 return 0; 143 } 144 145 // Alternative top-down approach where parent objects are created before 146 // their children. We only need to save one reference because the `axe` 147 // object is used in two places effectively making the buffer object 148 // graph a DAG. 149 int create_monster_top_down(flatcc_builder_t *B) 150 { 151 uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 152 size_t treasure_count = c_vec_len(treasure); 153 ns(Weapon_ref_t) axe; 154 155 // NOTE: if we use end_as_root, we MUST also start as root. 156 ns(Monster_start_as_root(B)); 157 ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); 158 ns(Monster_hp_add(B, 300)); 159 //ns(Monster_mana_add(B, 150)); 160 // We use create_str instead of add because we have no existing string reference. 161 ns(Monster_name_create_str(B, "Orc")); 162 // Again we use create because we no existing vector object, only a C-array. 163 ns(Monster_inventory_create(B, treasure, treasure_count)); 164 ns(Monster_color_add(B, ns(Color_Red))); 165 if (1) { 166 ns(Monster_weapons_start(B)); 167 ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Sword"), 3)); 168 // We reuse the axe object later. Note that we dereference a pointer 169 // because push always returns a short-term pointer to the stored element. 170 // We could also have created the axe object first and simply pushed it. 171 axe = *ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Axe"), 5)); 172 ns(Monster_weapons_end(B)); 173 } else { 174 // We can have more control with the table elements added to a vector: 175 // 176 ns(Monster_weapons_start(B)); 177 ns(Monster_weapons_push_start(B)); 178 ns(Weapon_name_create_str(B, "Sword")); 179 ns(Weapon_damage_add(B, 3)); 180 ns(Monster_weapons_push_end(B)); 181 ns(Monster_weapons_push_start(B)); 182 ns(Weapon_name_create_str(B, "Axe")); 183 ns(Weapon_damage_add(B, 5)); 184 axe = *ns(Monster_weapons_push_end(B)); 185 ns(Monster_weapons_end(B)); 186 } 187 // Unions can get their type by using a type-specific add/create/start method. 188 ns(Monster_equipped_Weapon_add(B, axe)); 189 190 ns(Monster_end_as_root(B)); 191 return 0; 192 } 193 194 // This isn't strictly needed because the builder already included the reader, 195 // but we would need it if our reader were in a separate file. 196 #include "monster_reader.h" 197 198 #undef ns 199 #define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. 200 201 int access_monster_buffer(const void *buffer) 202 { 203 // Note that we use the `table_t` suffix when reading a table object 204 // as opposed to the `ref_t` suffix used during the construction of 205 // the buffer. 206 ns(Monster_table_t) monster = ns(Monster_as_root(buffer)); 207 208 // Note: root object pointers are NOT the same as the `buffer` pointer. 209 210 // Make sure the buffer is accessible. 211 test_assert(monster != 0); 212 213 int16_t hp = ns(Monster_hp(monster)); 214 int16_t mana = ns(Monster_mana(monster)); 215 // This is just a const char *, but it also supports a fast length operation. 216 flatbuffers_string_t name = ns(Monster_name(monster)); 217 size_t name_len = flatbuffers_string_len(name); 218 219 test_assert(hp == 300); 220 // Since 150 is the default, we are reading a value that wasn't stored. 221 test_assert(mana == 150); 222 test_assert(0 == strcmp(name, "Orc")); 223 test_assert(name_len == strlen("Orc")); 224 225 int hp_present = ns(Monster_hp_is_present(monster)); // 1 226 int mana_present = ns(Monster_mana_is_present(monster)); // 0 227 test_assert(hp_present); 228 test_assert(!mana_present); 229 230 ns(Vec3_struct_t) pos = ns(Monster_pos(monster)); 231 // Make sure pos has been set. 232 test_assert(pos != 0); 233 float x = ns(Vec3_x(pos)); 234 float y = ns(Vec3_y(pos)); 235 float z = ns(Vec3_z(pos)); 236 237 // The literal `f` suffix is important because double literals does 238 // not always map cleanly to 32-bit represention even with only a few digits: 239 // `1.0 == 1.0f`, but `3.2 != 3.2f`. 240 test_assert(x == 1.0f); 241 test_assert(y == 2.0f); 242 test_assert(z == 3.0f); 243 244 // We can also read the position into a C-struct. We have to copy 245 // because we generally do not know if the native endian format 246 // matches the one stored in the buffer (pe: protocol endian). 247 ns(Vec3_t) pos_vec; 248 // `pe` indicates endian conversion from protocol to native. 249 ns(Vec3_copy_from_pe(&pos_vec, pos)); 250 test_assert(pos_vec.x == 1.0f); 251 test_assert(pos_vec.y == 2.0f); 252 test_assert(pos_vec.z == 3.0f); 253 254 // This is a const uint8_t *, but it shouldn't be accessed directly 255 // to ensure proper endian conversion. However, uint8 (ubyte) are 256 // not sensitive endianness, so we *could* have accessed it directly. 257 // The compiler likely optimizes this so that it doesn't matter. 258 flatbuffers_uint8_vec_t inv = ns(Monster_inventory(monster)); 259 size_t inv_len = flatbuffers_uint8_vec_len(inv); 260 // Make sure the inventory has been set. 261 test_assert(inv != 0); 262 // If `inv` were absent, the length would 0, so the above test is redundant. 263 test_assert(inv_len == 10); 264 // Index 0 is the first, index 2 is the third. 265 // NOTE: C++ uses the `Get` terminology for vector elemetns, C use `at`. 266 uint8_t third_item = flatbuffers_uint8_vec_at(inv, 2); 267 test_assert(third_item == 2); 268 269 ns(Weapon_vec_t) weapons = ns(Monster_weapons(monster)); 270 size_t weapons_len = ns(Weapon_vec_len(weapons)); 271 test_assert(weapons_len == 2); 272 // We can use `const char *` instead of `flatbuffers_string_t`. 273 const char *second_weapon_name = ns(Weapon_name(ns(Weapon_vec_at(weapons, 1)))); 274 int16_t second_weapon_damage = ns(Weapon_damage(ns(Weapon_vec_at(weapons, 1)))); 275 test_assert(second_weapon_name != 0 && strcmp(second_weapon_name, "Axe") == 0); 276 test_assert(second_weapon_damage == 5); 277 278 // Access union type field. 279 if (ns(Monster_equipped_type(monster)) == ns(Equipment_Weapon)) { 280 // Cast to appropriate type: 281 // C does not require the cast to Weapon_table_t, but C++ does. 282 ns(Weapon_table_t) weapon = (ns(Weapon_table_t)) ns(Monster_equipped(monster)); 283 const char *weapon_name = ns(Weapon_name(weapon)); 284 int16_t weapon_damage = ns(Weapon_damage(weapon)); 285 286 test_assert(0 == strcmp(weapon_name, "Axe")); 287 test_assert(weapon_damage == 5); 288 } 289 return 0; 290 } 291 292 #include <stdio.h> 293 294 int main(int argc, char *argv[]) 295 { 296 // Create a `FlatBufferBuilder`, which will be used to create our 297 // monsters' FlatBuffers. 298 flatcc_builder_t builder; 299 void *buf; 300 size_t size; 301 302 // Silence warnings. 303 (void)argc; 304 (void)argv; 305 306 // Initialize the builder object. 307 flatcc_builder_init(&builder); 308 test_assert(0 == create_monster_bottom_up(&builder, 0)); 309 310 // Allocate and extract a readable buffer from internal builder heap. 311 // NOTE: Finalizing the buffer does NOT change the builder, it 312 // just creates a snapshot of the builder content. 313 // NOTE2: finalize_buffer uses malloc while finalize_aligned_buffer 314 // uses a portable aligned allocation method. Often the malloc 315 // version is sufficient, but won't work for all schema on all 316 // systems. If the buffer is written to disk or network, but not 317 // accessed in memory, `finalize_buffer` is also sufficient. 318 // The flatcc_builder version of free or aligned_free should be used 319 // instead of `free` although free will often work on POSIX systems. 320 // This ensures portability and prevents issues when linking to 321 // allocation libraries other than malloc. 322 buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); 323 //buf = flatcc_builder_finalize_buffer(&builder, &size); 324 325 // We now have a FlatBuffer we can store on disk or send over a network. 326 // ** file/network code goes here :) ** 327 // Instead, we're going to access it right away (as if we just received it). 328 //access_monster_buffer(buf); 329 330 // prior to v0.5.0, use `aligned_free` 331 flatcc_builder_aligned_free(buf); 332 //free(buf); 333 // 334 // The builder object can optionally be reused after a reset which 335 // is faster than creating a new builder. Subsequent use might 336 // entirely avoid temporary allocations until finalizing the buffer. 337 flatcc_builder_reset(&builder); 338 test_assert(0 == create_monster_bottom_up(&builder, 1)); 339 buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); 340 access_monster_buffer(buf); 341 flatcc_builder_aligned_free(buf); 342 flatcc_builder_reset(&builder); 343 create_monster_top_down(&builder); 344 buf = flatcc_builder_finalize_buffer(&builder, &size); 345 test_assert(0 == access_monster_buffer(buf)); 346 flatcc_builder_free(buf); 347 // Eventually the builder must be cleaned up: 348 flatcc_builder_clear(&builder); 349 350 printf("The FlatBuffer was successfully created and accessed!\n"); 351 352 return 0; 353 }