Mutable FlatBuffers: in-place updates.

This commit contains the first step in providing mutable FlatBuffers,
non-const accessors and mutation functions for existing fields generated
from --gen-mutable.

Change-Id: Iebee3975f05c1001f8e22824725edeaa6d85fbee
Tested: on Linux.
Bug: 15777024
parent a8d6962a
......@@ -70,6 +70,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');});
<li><code>--strict-json</code> : Require &amp; generate strict JSON (field names are enclosed in quotes, no trailing commas in tables/vectors). By default, no quotes are required/generated, and trailing commas are allowed.</li>
<li><code>--no-prefix</code> : Don't prefix enum values in generated C++ by their enum type.</li>
<li><code>--gen-includes</code> : Generate include statements for included schemas the generated file depends on (C++).</li>
<li><code>--gen-mutable</code> : Generate additional non-const accessors for mutating FlatBuffers in-place.</li>
<li><code>--proto</code>: Expect input files to be .proto files (protocol buffers). Output the corresponding .fbs file. Currently supports: <code>package</code>, <code>message</code>, <code>enum</code>. Does not support, but will skip without error: <code>import</code>, <code>option</code>. Does not support, will generate error: <code>service</code>, <code>extend</code>, <code>extensions</code>, <code>oneof</code>, <code>group</code>, custom options, nested declarations. </li>
</ul>
</div></div><!-- contents -->
......
......@@ -102,7 +102,21 @@ $(document).ready(function(){initNavTree('md__cpp_usage.html','');});
<div class="fragment"><div class="line"><span class="keyword">auto</span> inv = monster-&gt;inventory();</div>
<div class="line">assert(inv);</div>
<div class="line">assert(inv-&gt;Get(9) == 9);</div>
</div><!-- fragment --><h3>Storing maps / dictionaries in a FlatBuffer</h3>
</div><!-- fragment --><h3>Mutating FlatBuffers</h3>
<p>As you saw above, typically once you have created a FlatBuffer, it is read-only from that moment on. There are however cases where you have just received a FlatBuffer, and you'd like to modify something about it before sending it on to another recipient. With the above functionality, you'd have to generate an entirely new FlatBuffer, while tracking what you modify in your own data structures. This is inconvenient.</p>
<p>For this reason FlatBuffers can also be mutated in-place. While this is great for making small fixes to an existing buffer, you generally want to create buffers from scratch whenever possible, since it is much more efficient and the API is much more general purpose.</p>
<p>To get non-const accessors, invoke <code>flatc</code> with <code>--gen-mutable</code>.</p>
<p>Similar to the reading API above, you now can:</p>
<div class="fragment"><div class="line"><span class="keyword">auto</span> monster = GetMutableMonster(buffer_pointer); <span class="comment">// non-const</span></div>
<div class="line">monster-&gt;mutate_hp(10); <span class="comment">// Set table field.</span></div>
<div class="line">monster-&gt;mutable_pos()-&gt;mutate_z(4); <span class="comment">// Set struct field.</span></div>
<div class="line">monster-&gt;mutable_inventory()-&gt;Mutate(0, 1); <span class="comment">// Set vector element.</span></div>
</div><!-- fragment --><p>We use the somewhat verbose term <code>mutate</code> instead of <code>set</code> to indicate that this is a special use case, not to be confused with the default way of constructing FlatBuffer data.</p>
<p>After the above mutations, you can send on the FlatBuffer to a new recipient without any further work!</p>
<p>Note that any <code>mutate_</code> functions on tables return a bool, which is false if the field we're trying to set isn't present in the buffer. Fields are not present if they weren't set, or even if they happen to be equal to the default value. For example, in the creation code above we set the <code>mana</code> field to <code>150</code>, which is the default value, so it was never stored in the buffer. Trying to call mutate_mana() on such data will return false, and the value won't actually be modified!</p>
<p>There's two ways around this. First, you can call <code>ForceDefaults()</code> on a <code>FlatBufferBuilder</code> to force all fields you set to actually be written. This of course increases the size of the buffer somewhat, but this may be acceptable for a mutable buffer.</p>
<p>Alternatively, you can use mutation functions that are able to insert fields and change the size of things. These functions are expensive however, since they need to resize the buffer and create new data.</p>
<h3>Storing maps / dictionaries in a FlatBuffer</h3>
<p>FlatBuffers doesn't support maps natively, but there is support to emulate their behavior with vectors and binary search, which means you can have fast lookups directly from a FlatBuffer without having to unpack your data into a <code>std::map</code> or similar.</p>
<p>To use it:</p><ul>
<li>Designate one of the fields in a table as they "key" field. You do this by setting the <code>key</code> attribute on this field, e.g. <code>name:string (key)</code>. You may only have one key field, and it must be of string or scalar type.</li>
......
......@@ -51,6 +51,9 @@ be generated for each file processed:
- `--gen-includes` : Generate include statements for included schemas the
generated file depends on (C++).
- `--gen-mutable` : Generate additional non-const accessors for mutating
FlatBuffers in-place.
- `--proto`: Expect input files to be .proto files (protocol buffers).
Output the corresponding .fbs file.
Currently supports: `package`, `message`, `enum`.
......
......@@ -163,6 +163,55 @@ Similarly, we can access elements of the inventory array:
assert(inv->Get(9) == 9);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Mutating FlatBuffers
As you saw above, typically once you have created a FlatBuffer, it is
read-only from that moment on. There are however cases where you have just
received a FlatBuffer, and you'd like to modify something about it before
sending it on to another recipient. With the above functionality, you'd have
to generate an entirely new FlatBuffer, while tracking what you modify in your
own data structures. This is inconvenient.
For this reason FlatBuffers can also be mutated in-place. While this is great
for making small fixes to an existing buffer, you generally want to create
buffers from scratch whenever possible, since it is much more efficient and
the API is much more general purpose.
To get non-const accessors, invoke `flatc` with `--gen-mutable`.
Similar to the reading API above, you now can:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
auto monster = GetMutableMonster(buffer_pointer); // non-const
monster->mutate_hp(10); // Set table field.
monster->mutable_pos()->mutate_z(4); // Set struct field.
monster->mutable_inventory()->Mutate(0, 1); // Set vector element.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We use the somewhat verbose term `mutate` instead of `set` to indicate that
this is a special use case, not to be confused with the default way of
constructing FlatBuffer data.
After the above mutations, you can send on the FlatBuffer to a new recipient
without any further work!
Note that any `mutate_` functions on tables return a bool, which is false
if the field we're trying to set isn't present in the buffer. Fields are not
present if they weren't set, or even if they happen to be equal to the
default value. For example, in the creation code above we set the `mana` field
to `150`, which is the default value, so it was never stored in the buffer.
Trying to call mutate_mana() on such data will return false, and the value won't
actually be modified!
There's two ways around this. First, you can call `ForceDefaults()` on a
`FlatBufferBuilder` to force all fields you set to actually be written. This
of course increases the size of the buffer somewhat, but this may be
acceptable for a mutable buffer.
Alternatively, you can use mutation functions that are able to insert fields
and change the size of things. These functions are expensive however, since
they need to resize the buffer and create new data.
### Storing maps / dictionaries in a FlatBuffer
FlatBuffers doesn't support maps natively, but there is support to
......
......@@ -289,11 +289,21 @@ public:
iterator end() { return iterator(Data(), length_); }
const_iterator end() const { return const_iterator(Data(), length_); }
// Change elements if you have a non-const pointer to this object.
void Mutate(uoffset_t i, T val) {
assert(i < size());
WriteScalar(Data() + i * sizeof(T), val);
}
// The raw data in little endian format. Use with care.
const uint8_t *Data() const {
return reinterpret_cast<const uint8_t *>(&length_ + 1);
}
uint8_t *Data() {
return reinterpret_cast<uint8_t *>(&length_ + 1);
}
template<typename K> return_type LookupByKey(K key) const {
auto span = size();
uoffset_t start = 0;
......@@ -806,11 +816,15 @@ class FlatBufferBuilder FLATBUFFERS_FINAL_CLASS {
bool force_defaults_; // Serialize values equal to their defaults anyway.
};
// Helper to get a typed pointer to the root object contained in the buffer.
template<typename T> const T *GetRoot(const void *buf) {
// Helpers to get a typed pointer to the root object contained in the buffer.
template<typename T> T *GetMutableRoot(void *buf) {
EndianCheck();
return reinterpret_cast<const T *>(reinterpret_cast<const uint8_t *>(buf) +
EndianScalar(*reinterpret_cast<const uoffset_t *>(buf)));
return reinterpret_cast<T *>(reinterpret_cast<uint8_t *>(buf) +
EndianScalar(*reinterpret_cast<uoffset_t *>(buf)));
}
template<typename T> const T *GetRoot(const void *buf) {
return GetMutableRoot<T>(const_cast<void *>(buf));
}
// Helper to see if the identifier in a buffer has the expected value.
......@@ -978,7 +992,7 @@ class Table {
return field_offset ? ReadScalar<T>(data_ + field_offset) : defaultval;
}
template<typename P> P GetPointer(voffset_t field) const {
template<typename P> P GetPointer(voffset_t field) {
auto field_offset = GetOptionalFieldOffset(field);
auto p = data_ + field_offset;
return field_offset
......@@ -986,18 +1000,21 @@ class Table {
: nullptr;
}
template<typename P> P GetPointer(voffset_t field) const {
return const_cast<Table *>(this)->GetPointer<P>(field);
}
template<typename P> P GetStruct(voffset_t field) const {
auto field_offset = GetOptionalFieldOffset(field);
return field_offset ? reinterpret_cast<P>(data_ + field_offset) : nullptr;
auto p = const_cast<u_int8_t *>(data_ + field_offset);
return field_offset ? reinterpret_cast<P>(p) : nullptr;
}
template<typename T> void SetField(voffset_t field, T val) {
template<typename T> bool SetField(voffset_t field, T val) {
auto field_offset = GetOptionalFieldOffset(field);
// If this asserts, you're trying to set a field that's not there
// (or should we return a bool instead?).
// check if it exists first using CheckField()
assert(field_offset);
if (!field_offset) return false;
WriteScalar(data_ + field_offset, val);
return true;
}
bool CheckField(voffset_t field) const {
......
......@@ -395,6 +395,7 @@ struct GeneratorOptions {
bool output_enum_identifiers;
bool prefixed_enums;
bool include_dependence_headers;
bool mutable_buffer;
// Possible options for the more general generator below.
enum Language { kJava, kCSharp, kGo, kMAX };
......@@ -404,6 +405,7 @@ struct GeneratorOptions {
GeneratorOptions() : strict_json(false), indent_step(2),
output_enum_identifiers(true), prefixed_enums(true),
include_dependence_headers(false),
mutable_buffer(false),
lang(GeneratorOptions::kJava) {}
};
......
......@@ -89,6 +89,7 @@ static void Error(const char *err, const char *obj, bool usage,
" --no-prefix Don\'t prefix enum values with the enum type in C++.\n"
" --gen-includes Generate include statements for included schemas the\n"
" generated file depends on (C++).\n"
" --gen-mutable Generate accessors that can mutate buffers in-place.\n"
" --proto Input is a .proto, translate to .fbs.\n"
"FILEs may depend on declarations in earlier files.\n"
"FILEs after the -- must be binary flatbuffer format files.\n"
......@@ -128,6 +129,8 @@ int main(int argc, const char *argv[]) {
opts.strict_json = true;
} else if(opt == "--no-prefix") {
opts.prefixed_enums = false;
} else if(opt == "--gen-mutable") {
opts.mutable_buffer = true;
} else if(opt == "--gen-includes") {
opts.include_dependence_headers = true;
} else if(opt == "--") { // Separator between text and binary inputs.
......
......@@ -221,22 +221,41 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
++it) {
auto &field = **it;
if (!field.deprecated) { // Deprecated fields won't be accessible.
auto is_scalar = IsScalar(field.value.type.base_type);
GenComment(field.doc_comment, code_ptr, nullptr, " ");
code += " " + GenTypeGet(parser, field.value.type, " ", "const ", " *",
true);
code += field.name + "() const { return ";
// Call a different accessor for pointers, that indirects.
std::string call = IsScalar(field.value.type.base_type)
auto accessor = is_scalar
? "GetField<"
: (IsStruct(field.value.type) ? "GetStruct<" : "GetPointer<");
call += GenTypeGet(parser, field.value.type, "", "const ", " *", false);
call += ">(" + NumToString(field.value.offset);
auto offsetstr = NumToString(field.value.offset);
auto call =
accessor +
GenTypeGet(parser, field.value.type, "", "const ", " *", false) +
">(" + offsetstr;
// Default value as second arg for non-pointer types.
if (IsScalar(field.value.type.base_type))
call += ", " + field.value.constant;
call += ")";
code += GenUnderlyingCast(parser, field, true, call);
code += "; }\n";
if (opts.mutable_buffer) {
if (is_scalar) {
code += " bool mutate_" + field.name + "(";
code += GenTypeBasic(parser, field.value.type, true);
code += " " + field.name + ") { return SetField(" + offsetstr + ", ";
code += GenUnderlyingCast(parser, field, false, field.name);
code += "); }\n";
} else {
auto type = GenTypeGet(parser, field.value.type, " ", "", " *", true);
code += " " + type + "mutable_" + field.name + "() { return ";
code += GenUnderlyingCast(parser, field, true,
accessor + type + ">(" + offsetstr + ")");
code += "; }\n";
}
}
auto nested = field.attributes.Lookup("nested_flatbuffer");
if (nested) {
auto nested_root = parser.structs_.Lookup(nested->constant);
......@@ -416,7 +435,8 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
code += " return builder_.Finish();\n}\n\n";
}
static void GenPadding(const FieldDef &field, const std::function<void (int bits)> &f) {
static void GenPadding(const FieldDef &field,
const std::function<void (int bits)> &f) {
if (field.padding) {
for (int i = 0; i < 4; i++)
if (static_cast<int>(field.padding) & (1 << i))
......@@ -427,7 +447,7 @@ static void GenPadding(const FieldDef &field, const std::function<void (int bits
// Generate an accessor struct with constructor for a flatbuffers struct.
static void GenStruct(const Parser &parser, StructDef &struct_def,
std::string *code_ptr) {
const GeneratorOptions &opts, std::string *code_ptr) {
if (struct_def.generated) return;
std::string &code = *code_ptr;
......@@ -502,14 +522,30 @@ static void GenStruct(const Parser &parser, StructDef &struct_def,
++it) {
auto &field = **it;
GenComment(field.doc_comment, code_ptr, nullptr, " ");
auto is_scalar = IsScalar(field.value.type.base_type);
code += " " + GenTypeGet(parser, field.value.type, " ", "const ", " &",
true);
code += field.name + "() const { return ";
code += GenUnderlyingCast(parser, field, true,
IsScalar(field.value.type.base_type)
is_scalar
? "flatbuffers::EndianScalar(" + field.name + "_)"
: field.name + "_");
code += "; }\n";
if (opts.mutable_buffer) {
if (is_scalar) {
code += " void mutate_" + field.name + "(";
code += GenTypeBasic(parser, field.value.type, true);
code += " " + field.name + ") { flatbuffers::WriteScalar(&";
code += field.name + "_, ";
code += GenUnderlyingCast(parser, field, false, field.name);
code += "); }\n";
} else {
code += " ";
code += GenTypeGet(parser, field.value.type, "", "", " &", true);
code += "mutable_" + field.name + "() { return " + field.name;
code += "_; }\n";
}
}
}
code += "};\nSTRUCT_END(" + struct_def.name + ", ";
code += NumToString(struct_def.bytesize) + ");\n\n";
......@@ -581,7 +617,7 @@ std::string GenerateCPP(const Parser &parser,
std::string decl_code;
for (auto it = parser.structs_.vec.begin();
it != parser.structs_.vec.end(); ++it) {
if ((**it).fixed) GenStruct(parser, **it, &decl_code);
if ((**it).fixed) GenStruct(parser, **it, opts, &decl_code);
}
for (auto it = parser.structs_.vec.begin();
it != parser.structs_.vec.end(); ++it) {
......@@ -654,6 +690,12 @@ std::string GenerateCPP(const Parser &parser,
code += name;
code += "(const void *buf) { return flatbuffers::GetRoot<";
code += name + ">(buf); }\n\n";
if (opts.mutable_buffer) {
code += "inline " + name + " *GetMutable";
code += name;
code += "(void *buf) { return flatbuffers::GetMutableRoot<";
code += name + ">(buf); }\n\n";
}
// The root verifier:
code += "inline bool Verify";
......
......@@ -57,7 +57,9 @@ MANUALLY_ALIGNED_STRUCT(2) Test FLATBUFFERS_FINAL_CLASS {
: a_(flatbuffers::EndianScalar(a)), b_(flatbuffers::EndianScalar(b)), __padding0(0) { (void)__padding0; }
int16_t a() const { return flatbuffers::EndianScalar(a_); }
void mutate_a(int16_t a) { flatbuffers::WriteScalar(&a_, a); }
int8_t b() const { return flatbuffers::EndianScalar(b_); }
void mutate_b(int8_t b) { flatbuffers::WriteScalar(&b_, b); }
};
STRUCT_END(Test, 4);
......@@ -78,18 +80,27 @@ MANUALLY_ALIGNED_STRUCT(16) Vec3 FLATBUFFERS_FINAL_CLASS {
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)), __padding0(0), test1_(flatbuffers::EndianScalar(test1)), test2_(flatbuffers::EndianScalar(static_cast<int8_t>(test2))), __padding1(0), test3_(test3), __padding2(0) { (void)__padding0; (void)__padding1; (void)__padding2; }
float x() const { return flatbuffers::EndianScalar(x_); }
void mutate_x(float x) { flatbuffers::WriteScalar(&x_, x); }
float y() const { return flatbuffers::EndianScalar(y_); }
void mutate_y(float y) { flatbuffers::WriteScalar(&y_, y); }
float z() const { return flatbuffers::EndianScalar(z_); }
void mutate_z(float z) { flatbuffers::WriteScalar(&z_, z); }
double test1() const { return flatbuffers::EndianScalar(test1_); }
void mutate_test1(double test1) { flatbuffers::WriteScalar(&test1_, test1); }
Color test2() const { return static_cast<Color>(flatbuffers::EndianScalar(test2_)); }
void mutate_test2(Color test2) { flatbuffers::WriteScalar(&test2_, static_cast<int8_t>(test2)); }
const Test &test3() const { return test3_; }
Test &mutable_test3() { return test3_; }
};
STRUCT_END(Vec3, 32);
struct Stat FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
const flatbuffers::String *id() const { return GetPointer<const flatbuffers::String *>(4); }
flatbuffers::String *mutable_id() { return GetPointer<flatbuffers::String *>(4); }
int64_t val() const { return GetField<int64_t>(6, 0); }
bool mutate_val(int64_t val) { return SetField(6, val); }
uint16_t count() const { return GetField<uint16_t>(8, 0); }
bool mutate_count(uint16_t count) { return SetField(8, count); }
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<flatbuffers::uoffset_t>(verifier, 4 /* id */) &&
......@@ -127,33 +138,56 @@ inline flatbuffers::Offset<Stat> CreateStat(flatbuffers::FlatBufferBuilder &_fbb
struct Monster FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
const Vec3 *pos() const { return GetStruct<const Vec3 *>(4); }
Vec3 *mutable_pos() { return GetStruct<Vec3 *>(4); }
int16_t mana() const { return GetField<int16_t>(6, 150); }
bool mutate_mana(int16_t mana) { return SetField(6, mana); }
int16_t hp() const { return GetField<int16_t>(8, 100); }
bool mutate_hp(int16_t hp) { return SetField(8, hp); }
const flatbuffers::String *name() const { return GetPointer<const flatbuffers::String *>(10); }
flatbuffers::String *mutable_name() { return GetPointer<flatbuffers::String *>(10); }
bool KeyCompareLessThan(const Monster *o) const { return *name() < *o->name(); }
int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); }
const flatbuffers::Vector<uint8_t> *inventory() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(14); }
flatbuffers::Vector<uint8_t> *mutable_inventory() { return GetPointer<flatbuffers::Vector<uint8_t> *>(14); }
Color color() const { return static_cast<Color>(GetField<int8_t>(16, 8)); }
bool mutate_color(Color color) { return SetField(16, static_cast<int8_t>(color)); }
Any test_type() const { return static_cast<Any>(GetField<uint8_t>(18, 0)); }
bool mutate_test_type(Any test_type) { return SetField(18, static_cast<uint8_t>(test_type)); }
const void *test() const { return GetPointer<const void *>(20); }
void *mutable_test() { return GetPointer<void *>(20); }
const flatbuffers::Vector<const Test *> *test4() const { return GetPointer<const flatbuffers::Vector<const Test *> *>(22); }
flatbuffers::Vector<const Test *> *mutable_test4() { return GetPointer<flatbuffers::Vector<const Test *> *>(22); }
const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *testarrayofstring() const { return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(24); }
flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *mutable_testarrayofstring() { return GetPointer<flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(24); }
/// an example documentation comment: this will end up in the generated code
/// multiline too
const flatbuffers::Vector<flatbuffers::Offset<Monster>> *testarrayoftables() const { return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<Monster>> *>(26); }
flatbuffers::Vector<flatbuffers::Offset<Monster>> *mutable_testarrayoftables() { return GetPointer<flatbuffers::Vector<flatbuffers::Offset<Monster>> *>(26); }
const Monster *enemy() const { return GetPointer<const Monster *>(28); }
Monster *mutable_enemy() { return GetPointer<Monster *>(28); }
const flatbuffers::Vector<uint8_t> *testnestedflatbuffer() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(30); }
flatbuffers::Vector<uint8_t> *mutable_testnestedflatbuffer() { return GetPointer<flatbuffers::Vector<uint8_t> *>(30); }
const Monster *testnestedflatbuffer_nested_root() const { return flatbuffers::GetRoot<Monster>(testnestedflatbuffer()->Data()); }
const Stat *testempty() const { return GetPointer<const Stat *>(32); }
Stat *mutable_testempty() { return GetPointer<Stat *>(32); }
uint8_t testbool() const { return GetField<uint8_t>(34, 0); }
bool mutate_testbool(uint8_t testbool) { return SetField(34, testbool); }
int32_t testhashs32_fnv1() const { return GetField<int32_t>(36, 0); }
bool mutate_testhashs32_fnv1(int32_t testhashs32_fnv1) { return SetField(36, testhashs32_fnv1); }
uint32_t testhashu32_fnv1() const { return GetField<uint32_t>(38, 0); }
bool mutate_testhashu32_fnv1(uint32_t testhashu32_fnv1) { return SetField(38, testhashu32_fnv1); }
int64_t testhashs64_fnv1() const { return GetField<int64_t>(40, 0); }
bool mutate_testhashs64_fnv1(int64_t testhashs64_fnv1) { return SetField(40, testhashs64_fnv1); }
uint64_t testhashu64_fnv1() const { return GetField<uint64_t>(42, 0); }
bool mutate_testhashu64_fnv1(uint64_t testhashu64_fnv1) { return SetField(42, testhashu64_fnv1); }
int32_t testhashs32_fnv1a() const { return GetField<int32_t>(44, 0); }
bool mutate_testhashs32_fnv1a(int32_t testhashs32_fnv1a) { return SetField(44, testhashs32_fnv1a); }
uint32_t testhashu32_fnv1a() const { return GetField<uint32_t>(46, 0); }
bool mutate_testhashu32_fnv1a(uint32_t testhashu32_fnv1a) { return SetField(46, testhashu32_fnv1a); }
int64_t testhashs64_fnv1a() const { return GetField<int64_t>(48, 0); }
bool mutate_testhashs64_fnv1a(int64_t testhashs64_fnv1a) { return SetField(48, testhashs64_fnv1a); }
uint64_t testhashu64_fnv1a() const { return GetField<uint64_t>(50, 0); }
bool mutate_testhashu64_fnv1a(uint64_t testhashu64_fnv1a) { return SetField(50, testhashu64_fnv1a); }
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<Vec3>(verifier, 4 /* pos */) &&
......@@ -290,6 +324,8 @@ inline bool VerifyAny(flatbuffers::Verifier &verifier, const void *union_obj, An
inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<Monster>(buf); }
inline Monster *GetMutableMonster(void *buf) { return flatbuffers::GetMutableRoot<Monster>(buf); }
inline bool VerifyMonsterBuffer(flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer<Monster>(); }
inline const char *MonsterIdentifier() { return "MONS"; }
......
......@@ -127,7 +127,7 @@ flatbuffers::unique_ptr_t CreateFlatBufferTest(std::string &buffer) {
}
// example of accessing a buffer loaded in memory:
void AccessFlatBufferTest(const uint8_t *flatbuf, const std::size_t length) {
void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length) {
// First, verify the buffers integrity (optional)
flatbuffers::Verifier verifier(flatbuf, length);
......@@ -159,6 +159,8 @@ void AccessFlatBufferTest(const uint8_t *flatbuf, const std::size_t length) {
for (auto it = inventory->begin(); it != inventory->end(); ++it)
TEST_EQ(*it, inv_data[it - inventory->begin()]);
TEST_EQ(monster->color(), Color_Blue);
// Example of accessing a union:
TEST_EQ(monster->test_type(), Any_Monster); // First make sure which it is.
auto monster2 = reinterpret_cast<const Monster *>(monster->test());
......@@ -200,7 +202,39 @@ void AccessFlatBufferTest(const uint8_t *flatbuf, const std::size_t length) {
for (auto it = tests->begin(); it != tests->end(); ++it) {
TEST_EQ(it->a() == 10 || it->a() == 30, true); // Just testing iterators.
}
}
// Change a FlatBuffer in-place, after it has been constructed.
void MutateFlatBuffersTest(uint8_t *flatbuf, std::size_t length) {
// Get non-const pointer to root.
auto monster = GetMutableMonster(flatbuf);
// Each of these tests mutates, then tests, then set back to the original,
// so we can test that the buffer in the end still passes our original test.
auto hp_ok = monster->mutate_hp(10);
TEST_EQ(hp_ok, true); // Field was present.
TEST_EQ(monster->hp(), 10);
monster->mutate_hp(80);
auto mana_ok = monster->mutate_mana(10);
TEST_EQ(mana_ok, false); // Field was NOT present, because default value.
// Mutate structs.
auto pos = monster->mutable_pos();
auto test3 = pos->mutable_test3(); // Struct inside a struct.
test3.mutate_a(50); // Struct fields never fail.
TEST_EQ(test3.a(), 50);
test3.mutate_a(10);
// Mutate vectors.
auto inventory = monster->mutable_inventory();
inventory->Mutate(9, 100);
TEST_EQ(inventory->Get(9), 100);
inventory->Mutate(9, 9);
// Run the verifier and the regular test to make sure we didn't trample on
// anything.
AccessFlatBufferTest(flatbuf, length);
}
// example of parsing text straight into a buffer, and generating
......@@ -626,6 +660,8 @@ int main(int /*argc*/, const char * /*argv*/[]) {
rawbuf.length());
AccessFlatBufferTest(flatbuf.get(), rawbuf.length());
MutateFlatBuffersTest(flatbuf.get(), rawbuf.length());
#ifndef __ANDROID__ // requires file access
ParseAndGenerateTextTest();
ParseProtoTest();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment