nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

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 }