From 7bfed4b29a425b183e2774919ced9e0402fdda10 Mon Sep 17 00:00:00 2001 From: Maor Itzkovitch Date: Sat, 15 Aug 2015 14:48:47 +0300 Subject: [PATCH] added vector mutators --- docs/source/JavaUsage.md | 45 +++++++++++++++ src/idl_gen_general.cpp | 70 ++++++++++++++--------- tests/FlatBuffers.Test/FlatBuffersExampleTests.cs | 19 ++++++ tests/JavaTest.java | 18 ++++++ tests/MyGame/Example/Monster.cs | 2 + tests/MyGame/Example/Monster.java | 2 + 6 files changed, 130 insertions(+), 26 deletions(-) diff --git a/docs/source/JavaUsage.md b/docs/source/JavaUsage.md index 6ffb14b..4819e5b 100755 --- a/docs/source/JavaUsage.md +++ b/docs/source/JavaUsage.md @@ -177,3 +177,48 @@ There currently is no support for parsing text (Schema's and JSON) directly from Java or C#, though you could use the C++ parser through native call interfaces available to each language. Please see the C++ documentation for more on text parsing. + +### 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`. + +You now can: + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.java} + Monster monster = Monster.getRootAsMonster(bb); + monster.mutateHp(10); // Set table field. + monster.pos().mutateZ(4); // Set struct field. + monster.mutateInventory(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 boolean, 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 mutateMana() on such data will return false, and the value won't +actually be modified! + +One way to solve this is to 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. diff --git a/src/idl_gen_general.cpp b/src/idl_gen_general.cpp index fb55a80..86440b1 100644 --- a/src/idl_gen_general.cpp +++ b/src/idl_gen_general.cpp @@ -252,7 +252,7 @@ static Type DestinationType(const LanguageParameters &lang, const Type &type, bool vectorelem) { if (lang.language != GeneratorOptions::kJava) return type; switch (type.base_type) { - case BASE_TYPE_UCHAR: return Type(BASE_TYPE_INT); + case BASE_TYPE_UCHAR: return Type(BASE_TYPE_INT); //intentionally returns int to avoid unnecessary casting in Java case BASE_TYPE_USHORT: return Type(BASE_TYPE_INT); case BASE_TYPE_UINT: return Type(BASE_TYPE_LONG); case BASE_TYPE_VECTOR: @@ -367,21 +367,25 @@ static std::string DestinationValue(const LanguageParameters &lang, // In C#, one cast directly cast an Enum to its underlying type, which is essential before putting it onto the buffer. static std::string SourceCast(const LanguageParameters &lang, const Type &type) { - switch (lang.language) { - case GeneratorOptions::kJava: - if (type.base_type == BASE_TYPE_UINT) return "(int)"; - else if (type.base_type == BASE_TYPE_USHORT) return "(short)"; - else if (type.base_type == BASE_TYPE_UCHAR) return "(byte)"; - break; - case GeneratorOptions::kCSharp: - if (type.enum_def != nullptr && - type.base_type != BASE_TYPE_UNION) - return "(" + GenTypeGet(lang, type) + ")"; - break; - default: - break; + if (type.base_type == BASE_TYPE_VECTOR) { + return SourceCast(lang, type.VectorType()); + } else { + switch (lang.language) { + case GeneratorOptions::kJava: + if (type.base_type == BASE_TYPE_UINT) return "(int)"; + else if (type.base_type == BASE_TYPE_USHORT) return "(short)"; + else if (type.base_type == BASE_TYPE_UCHAR) return "(byte)"; + break; + case GeneratorOptions::kCSharp: + if (type.enum_def != nullptr && + type.base_type != BASE_TYPE_UNION) + return "(" + GenTypeGet(lang, type) + ")"; + break; + default: + break; + } + return ""; } - return ""; } static std::string GenDefaultValue(const LanguageParameters &lang, const Value &value, bool for_buffer) { @@ -640,6 +644,7 @@ static void GenStruct(const LanguageParameters &lang, const Parser &parser, std::string src_cast = SourceCast(lang, field.value.type); std::string method_start = " public " + type_name_dest + " " + MakeCamel(field.name, lang.first_camel_upper); + // Most field accessors need to retrieve and test the field offset first, // this is the prefix code for that: auto offset_prefix = " { int o = __offset(" + @@ -784,24 +789,37 @@ static void GenStruct(const LanguageParameters &lang, const Parser &parser, code += "); }\n"; } - // generate mutators for scalar fields + // generate mutators for scalar fields or vectors of scalars if (opts.mutable_buffer) { + auto underlying_type = field.value.type.base_type == BASE_TYPE_VECTOR + ? field.value.type.VectorType() + : field.value.type; // boolean parameters have to be explicitly converted to byte representation - std::string setter_parameter = field.value.type.base_type == BASE_TYPE_BOOL ? "(byte)(" + field.name + " ? 1 : 0)" : field.name; - std::string mutator_prefix = MakeCamel("mutate", lang.first_camel_upper); - if (IsScalar(field.value.type.base_type)) { + auto setter_parameter = underlying_type.base_type == BASE_TYPE_BOOL ? "(byte)(" + field.name + " ? 1 : 0)" : field.name; + auto mutator_prefix = MakeCamel("mutate", lang.first_camel_upper); + //a vector mutator also needs the index of the vector element it should mutate + auto mutator_params = (field.value.type.base_type == BASE_TYPE_VECTOR ? "(int j, " : "(") + + GenTypeNameDest(lang, underlying_type) + " " + + field.name + ") { "; + auto setter_index = field.value.type.base_type == BASE_TYPE_VECTOR + ? "__vector(o) + j * " + NumToString(InlineSize(underlying_type)) + : (struct_def.fixed ? "bb_pos + " + NumToString(field.value.offset) : "o + bb_pos"); + + + if (IsScalar(field.value.type.base_type) || + (field.value.type.base_type == BASE_TYPE_VECTOR && + IsScalar(field.value.type.VectorType().base_type))) { code += " public "; code += struct_def.fixed ? "void " : lang.bool_type; - code += mutator_prefix + MakeCamel(field.name, true) + "("; - code += GenTypeNameDest(lang, field.value.type); - code += " " + field.name + ") { "; + code += mutator_prefix + MakeCamel(field.name, true); + code += mutator_params; if (struct_def.fixed) { - code += GenSetter(lang, field.value.type) + "(bb_pos + "; - code += NumToString(field.value.offset) + ", " + src_cast + setter_parameter + "); }\n"; + code += GenSetter(lang, underlying_type) + "(" + setter_index + ", "; + code += src_cast + setter_parameter + "); }\n"; } else { code += "int o = __offset(" + NumToString(field.value.offset) + ");"; - code += " if (o != 0) { " + GenSetter(lang, field.value.type); - code += "(o + bb_pos, " + src_cast + setter_parameter + "); return true; } else { return false; } }\n"; + code += " if (o != 0) { " + GenSetter(lang, underlying_type); + code += "(" + setter_index + ", " + src_cast + setter_parameter + "); return true; } else { return false; } }\n"; } } } diff --git a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs index 436180c..666ac1c 100644 --- a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs +++ b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs @@ -107,6 +107,25 @@ namespace FlatBuffers.Test Assert.AreEqual(monster.MutateTestType(Any.Monster), true); Assert.AreEqual(monster.TestType, Any.Monster); + //mutate the inventory vector + Assert.AreEqual(monster.MutateInventory(0, 1), true); + Assert.AreEqual(monster.MutateInventory(1, 2), true); + Assert.AreEqual(monster.MutateInventory(2, 3), true); + Assert.AreEqual(monster.MutateInventory(3, 4), true); + Assert.AreEqual(monster.MutateInventory(4, 5), true); + + for (int i = 0; i < monster.InventoryLength; i++) + { + Assert.AreEqual(monster.GetInventory(i), i + 1); + } + + //reverse mutation + Assert.AreEqual(monster.MutateInventory(0, 0), true); + Assert.AreEqual(monster.MutateInventory(1, 1), true); + Assert.AreEqual(monster.MutateInventory(2, 2), true); + Assert.AreEqual(monster.MutateInventory(3, 3), true); + Assert.AreEqual(monster.MutateInventory(4, 4), true); + // get a struct field and edit one of its fields Vec3 pos = monster.Pos; Assert.AreEqual(pos.X, 1.0f); diff --git a/tests/JavaTest.java b/tests/JavaTest.java index 6ac11ea..e017016 100755 --- a/tests/JavaTest.java +++ b/tests/JavaTest.java @@ -127,6 +127,24 @@ class JavaTest { TestEq(monster.mutateTestType(Any.Monster), true); TestEq(monster.testType(), (byte)Any.Monster); + //mutate the inventory vector + TestEq(monster.mutateInventory(0, 1), true); + TestEq(monster.mutateInventory(1, 2), true); + TestEq(monster.mutateInventory(2, 3), true); + TestEq(monster.mutateInventory(3, 4), true); + TestEq(monster.mutateInventory(4, 5), true); + + for (int i = 0; i < monster.inventoryLength(); i++) { + TestEq(monster.inventory(i), i + 1); + } + + //reverse mutation + TestEq(monster.mutateInventory(0, 0), true); + TestEq(monster.mutateInventory(1, 1), true); + TestEq(monster.mutateInventory(2, 2), true); + TestEq(monster.mutateInventory(3, 3), true); + TestEq(monster.mutateInventory(4, 4), true); + // get a struct field and edit one of its fields Vec3 pos = monster.pos(); TestEq(pos.x(), 1.0f); diff --git a/tests/MyGame/Example/Monster.cs b/tests/MyGame/Example/Monster.cs index ef0ef92..975e4d0 100644 --- a/tests/MyGame/Example/Monster.cs +++ b/tests/MyGame/Example/Monster.cs @@ -20,6 +20,7 @@ public sealed class Monster : Table { public string Name { get { int o = __offset(10); return o != 0 ? __string(o + bb_pos) : null; } } public byte GetInventory(int j) { int o = __offset(14); return o != 0 ? bb.Get(__vector(o) + j * 1) : (byte)0; } public int InventoryLength { get { int o = __offset(14); return o != 0 ? __vector_len(o) : 0; } } + public bool MutateInventory(int j, byte inventory) { int o = __offset(14); if (o != 0) { bb.Put(__vector(o) + j * 1, inventory); return true; } else { return false; } } public Color Color { get { int o = __offset(16); return o != 0 ? (Color)bb.GetSbyte(o + bb_pos) : (Color)8; } } public bool MutateColor(Color color) { int o = __offset(16); if (o != 0) { bb.PutSbyte(o + bb_pos, (sbyte)color); return true; } else { return false; } } public Any TestType { get { int o = __offset(18); return o != 0 ? (Any)bb.Get(o + bb_pos) : (Any)0; } } @@ -39,6 +40,7 @@ public sealed class Monster : Table { public Monster GetEnemy(Monster obj) { int o = __offset(28); return o != 0 ? obj.__init(__indirect(o + bb_pos), bb) : null; } public byte GetTestnestedflatbuffer(int j) { int o = __offset(30); return o != 0 ? bb.Get(__vector(o) + j * 1) : (byte)0; } public int TestnestedflatbufferLength { get { int o = __offset(30); return o != 0 ? __vector_len(o) : 0; } } + public bool MutateTestnestedflatbuffer(int j, byte testnestedflatbuffer) { int o = __offset(30); if (o != 0) { bb.Put(__vector(o) + j * 1, testnestedflatbuffer); return true; } else { return false; } } public Stat Testempty { get { return GetTestempty(new Stat()); } } public Stat GetTestempty(Stat obj) { int o = __offset(32); return o != 0 ? obj.__init(__indirect(o + bb_pos), bb) : null; } public bool Testbool { get { int o = __offset(34); return o != 0 ? 0!=bb.Get(o + bb_pos) : (bool)false; } } diff --git a/tests/MyGame/Example/Monster.java b/tests/MyGame/Example/Monster.java index ad64e78..0a5f7dd 100644 --- a/tests/MyGame/Example/Monster.java +++ b/tests/MyGame/Example/Monster.java @@ -24,6 +24,7 @@ public final class Monster extends Table { public int inventory(int j) { int o = __offset(14); return o != 0 ? bb.get(__vector(o) + j * 1) & 0xFF : 0; } public int inventoryLength() { int o = __offset(14); return o != 0 ? __vector_len(o) : 0; } public ByteBuffer inventoryAsByteBuffer() { return __vector_as_bytebuffer(14, 1); } + public boolean mutateInventory(int j, int inventory) { int o = __offset(14); if (o != 0) { bb.put(__vector(o) + j * 1, (byte)inventory); return true; } else { return false; } } public byte color() { int o = __offset(16); return o != 0 ? bb.get(o + bb_pos) : 8; } public boolean mutateColor(byte color) { int o = __offset(16); if (o != 0) { bb.put(o + bb_pos, color); return true; } else { return false; } } public byte testType() { int o = __offset(18); return o != 0 ? bb.get(o + bb_pos) : 0; } @@ -46,6 +47,7 @@ public final class Monster extends Table { public int testnestedflatbuffer(int j) { int o = __offset(30); return o != 0 ? bb.get(__vector(o) + j * 1) & 0xFF : 0; } public int testnestedflatbufferLength() { int o = __offset(30); return o != 0 ? __vector_len(o) : 0; } public ByteBuffer testnestedflatbufferAsByteBuffer() { return __vector_as_bytebuffer(30, 1); } + public boolean mutateTestnestedflatbuffer(int j, int testnestedflatbuffer) { int o = __offset(30); if (o != 0) { bb.put(__vector(o) + j * 1, (byte)testnestedflatbuffer); return true; } else { return false; } } public Stat testempty() { return testempty(new Stat()); } public Stat testempty(Stat obj) { int o = __offset(32); return o != 0 ? obj.__init(__indirect(o + bb_pos), bb) : null; } public boolean testbool() { int o = __offset(34); return o != 0 ? 0!=bb.get(o + bb_pos) : false; } -- 2.7.4