From 199a49b5b3ec090f229258ac78fedc842ac9f07d Mon Sep 17 00:00:00 2001 From: gonzaloserrano Date: Thu, 7 Jul 2016 12:46:39 +0200 Subject: [PATCH] Add a generic way to deserialize a flatbuffer in Go. Similar to what protobufs does with its `Message` interface, introduce here such interface and create a generic `GetRootAs` method to deserialize a flatbuffer. --- go/lib.go | 13 + src/idl_gen_go.cpp | 22 ++ tests/MyGame/Example/Monster.go | 4 + tests/MyGame/Example/Stat.go | 4 + tests/MyGame/Example/Test.go | 4 + .../MyGame/Example/TestSimpleTableWithEnum.go | 4 + tests/MyGame/Example/Vec3.go | 4 + tests/MyGame/Example2/Monster.go | 4 + tests/go_test.go | 317 ++++++++++-------- 9 files changed, 236 insertions(+), 140 deletions(-) create mode 100644 go/lib.go diff --git a/go/lib.go b/go/lib.go new file mode 100644 index 000000000..adfce52ef --- /dev/null +++ b/go/lib.go @@ -0,0 +1,13 @@ +package flatbuffers + +// FlatBuffer is the interface that represents a flatbuffer. +type FlatBuffer interface { + Table() Table + Init(buf []byte, i UOffsetT) +} + +// GetRootAs is a generic helper to initialize a FlatBuffer with the provided buffer bytes and its data offset. +func GetRootAs(buf []byte, offset UOffsetT, fb FlatBuffer) { + n := GetUOffsetT(buf[offset:]) + fb.Init(buf, n+offset) +} diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index 573300980..73f930f28 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -144,6 +144,23 @@ static void InitializeExisting(const StructDef &struct_def, code += "}\n\n"; } +// Implement the table accessor +static void GenTableAccessor(const StructDef &struct_def, + std::string *code_ptr) { + std::string &code = *code_ptr; + + GenReceiver(struct_def, code_ptr); + code += " Table() flatbuffers.Table "; + code += "{\n"; + + if (struct_def.fixed) { + code += "\treturn rcv._tab.Table\n"; + } else { + code += "\treturn rcv._tab\n"; + } + code += "}\n\n"; +} + // Get the length of a vector. static void GetVectorLen(const StructDef &struct_def, const FieldDef &field, @@ -594,6 +611,10 @@ static void GenStruct(const StructDef &struct_def, // Generate the Init method that sets the field in a pre-existing // accessor object. This is to allow object reuse. InitializeExisting(struct_def, code_ptr); + // Generate _tab accessor + GenTableAccessor(struct_def, code_ptr); + + // Generate struct fields accessors for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { @@ -604,6 +625,7 @@ static void GenStruct(const StructDef &struct_def, GenStructMutator(struct_def, field, code_ptr); } + // Generate builders if (struct_def.fixed) { // create a struct constructor function GenStructBuilder(struct_def, code_ptr); diff --git a/tests/MyGame/Example/Monster.go b/tests/MyGame/Example/Monster.go index 7ba062fdc..4989eb4f8 100644 --- a/tests/MyGame/Example/Monster.go +++ b/tests/MyGame/Example/Monster.go @@ -23,6 +23,10 @@ func (rcv *Monster) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *Monster) Table() flatbuffers.Table { + return rcv._tab +} + func (rcv *Monster) Pos(obj *Vec3) *Vec3 { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { diff --git a/tests/MyGame/Example/Stat.go b/tests/MyGame/Example/Stat.go index 022c4f036..9abc558a4 100644 --- a/tests/MyGame/Example/Stat.go +++ b/tests/MyGame/Example/Stat.go @@ -22,6 +22,10 @@ func (rcv *Stat) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *Stat) Table() flatbuffers.Table { + return rcv._tab +} + func (rcv *Stat) Id() []byte { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { diff --git a/tests/MyGame/Example/Test.go b/tests/MyGame/Example/Test.go index 781db8b09..cb283fbdf 100644 --- a/tests/MyGame/Example/Test.go +++ b/tests/MyGame/Example/Test.go @@ -15,6 +15,10 @@ func (rcv *Test) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *Test) Table() flatbuffers.Table { + return rcv._tab.Table +} + func (rcv *Test) A() int16 { return rcv._tab.GetInt16(rcv._tab.Pos + flatbuffers.UOffsetT(0)) } diff --git a/tests/MyGame/Example/TestSimpleTableWithEnum.go b/tests/MyGame/Example/TestSimpleTableWithEnum.go index be50d5ca1..0704b70ef 100644 --- a/tests/MyGame/Example/TestSimpleTableWithEnum.go +++ b/tests/MyGame/Example/TestSimpleTableWithEnum.go @@ -22,6 +22,10 @@ func (rcv *TestSimpleTableWithEnum) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *TestSimpleTableWithEnum) Table() flatbuffers.Table { + return rcv._tab +} + func (rcv *TestSimpleTableWithEnum) Color() int8 { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { diff --git a/tests/MyGame/Example/Vec3.go b/tests/MyGame/Example/Vec3.go index 1838e5606..a880a4077 100644 --- a/tests/MyGame/Example/Vec3.go +++ b/tests/MyGame/Example/Vec3.go @@ -15,6 +15,10 @@ func (rcv *Vec3) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *Vec3) Table() flatbuffers.Table { + return rcv._tab.Table +} + func (rcv *Vec3) X() float32 { return rcv._tab.GetFloat32(rcv._tab.Pos + flatbuffers.UOffsetT(0)) } diff --git a/tests/MyGame/Example2/Monster.go b/tests/MyGame/Example2/Monster.go index 698c2ef56..79949b901 100644 --- a/tests/MyGame/Example2/Monster.go +++ b/tests/MyGame/Example2/Monster.go @@ -22,6 +22,10 @@ func (rcv *Monster) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } +func (rcv *Monster) Table() flatbuffers.Table { + return rcv._tab +} + func MonsterStart(builder *flatbuffers.Builder) { builder.StartObject(0) } diff --git a/tests/go_test.go b/tests/go_test.go index 75d6b1f57..9aba3e1bc 100644 --- a/tests/go_test.go +++ b/tests/go_test.go @@ -78,6 +78,7 @@ func TestAll(t *testing.T) { // Verify that GetRootAs works for non-root tables CheckGetRootAsForNonRootTable(t.Fatalf) + CheckTableAccessors(t.Fatalf) // Verify that using the generated Go code builds a buffer without // returning errors: @@ -137,155 +138,159 @@ func TestAll(t *testing.T) { // CheckReadBuffer checks that the given buffer is evaluated correctly // as the example Monster. func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string, ...interface{})) { - monster := example.GetRootAsMonster(buf, offset) - - if got := monster.Hp(); 80 != got { - fail(FailString("hp", 80, got)) - } - - // default - if got := monster.Mana(); 150 != got { - fail(FailString("mana", 150, got)) - } - - if got := monster.Name(); !bytes.Equal([]byte("MyMonster"), got) { - fail(FailString("name", "MyMonster", got)) - } - - // initialize a Vec3 from Pos() - vec := new(example.Vec3) - vec = monster.Pos(vec) - if vec == nil { - fail("vec3 initialization failed") - } - - // check that new allocs equal given ones: - vec2 := monster.Pos(nil) - if !reflect.DeepEqual(vec, vec2) { - fail("fresh allocation failed") - } - - // verify the properties of the Vec3 - if got := vec.X(); float32(1.0) != got { - fail(FailString("Pos.X", float32(1.0), got)) - } - - if got := vec.Y(); float32(2.0) != got { - fail(FailString("Pos.Y", float32(2.0), got)) - } - - if got := vec.Z(); float32(3.0) != got { - fail(FailString("Pos.Z", float32(3.0), got)) - } - - if got := vec.Test1(); float64(3.0) != got { - fail(FailString("Pos.Test1", float64(3.0), got)) - } - - if got := vec.Test2(); int8(2) != got { - fail(FailString("Pos.Test2", int8(2), got)) - } - - // initialize a Test from Test3(...) - t := new(example.Test) - t = vec.Test3(t) - if t == nil { - fail("vec.Test3(&t) failed") - } - - // check that new allocs equal given ones: - t2 := vec.Test3(nil) - if !reflect.DeepEqual(t, t2) { - fail("fresh allocation failed") - } - - // verify the properties of the Test - if got := t.A(); int16(5) != got { - fail(FailString("t.A()", int16(5), got)) - } - - if got := t.B(); int8(6) != got { - fail(FailString("t.B()", int8(6), got)) - } - - if got := monster.TestType(); example.AnyMonster != got { - fail(FailString("monster.TestType()", example.AnyMonster, got)) - } - - // initialize a Table from a union field Test(...) - var table2 flatbuffers.Table - if ok := monster.Test(&table2); !ok { - fail("monster.Test(&monster2) failed") - } - - // initialize a Monster from the Table from the union - var monster2 example.Monster - monster2.Init(table2.Bytes, table2.Pos) - - if got := monster2.Name(); !bytes.Equal([]byte("Fred"), got) { - fail(FailString("monster2.Name()", "Fred", got)) - } - - inventorySlice := monster.InventoryBytes() - if len(inventorySlice) != monster.InventoryLength() { - fail(FailString("len(monster.InventoryBytes) != monster.InventoryLength", len(inventorySlice), monster.InventoryLength())) - } - - if got := monster.InventoryLength(); 5 != got { - fail(FailString("monster.InventoryLength", 5, got)) - } - - invsum := 0 - l := monster.InventoryLength() - for i := 0; i < l; i++ { - v := monster.Inventory(i) - if v != inventorySlice[i] { - fail(FailString("monster inventory slice[i] != Inventory(i)", v, inventorySlice[i])) + // try the two ways of generating a monster + monster1 := example.GetRootAsMonster(buf, offset) + monster2 := &example.Monster{} + flatbuffers.GetRootAs(buf, offset, monster2) + for _, monster := range []*example.Monster{monster1, monster2} { + if got := monster.Hp(); 80 != got { + fail(FailString("hp", 80, got)) } - invsum += int(v) - } - if invsum != 10 { - fail(FailString("monster inventory sum", 10, invsum)) - } - if got := monster.Test4Length(); 2 != got { - fail(FailString("monster.Test4Length()", 2, got)) - } + // default + if got := monster.Mana(); 150 != got { + fail(FailString("mana", 150, got)) + } - var test0 example.Test - ok := monster.Test4(&test0, 0) - if !ok { - fail(FailString("monster.Test4(&test0, 0)", true, ok)) - } + if got := monster.Name(); !bytes.Equal([]byte("MyMonster"), got) { + fail(FailString("name", "MyMonster", got)) + } - var test1 example.Test - ok = monster.Test4(&test1, 1) - if !ok { - fail(FailString("monster.Test4(&test1, 1)", true, ok)) - } + // initialize a Vec3 from Pos() + vec := new(example.Vec3) + vec = monster.Pos(vec) + if vec == nil { + fail("vec3 initialization failed") + } - // the position of test0 and test1 are swapped in monsterdata_java_wire - // and monsterdata_test_wire, so ignore ordering - v0 := test0.A() - v1 := test0.B() - v2 := test1.A() - v3 := test1.B() - sum := int(v0) + int(v1) + int(v2) + int(v3) + // check that new allocs equal given ones: + vec2 := monster.Pos(nil) + if !reflect.DeepEqual(vec, vec2) { + fail("fresh allocation failed") + } - if 100 != sum { - fail(FailString("test0 and test1 sum", 100, sum)) - } + // verify the properties of the Vec3 + if got := vec.X(); float32(1.0) != got { + fail(FailString("Pos.X", float32(1.0), got)) + } - if got := monster.TestarrayofstringLength(); 2 != got { - fail(FailString("Testarrayofstring length", 2, got)) - } + if got := vec.Y(); float32(2.0) != got { + fail(FailString("Pos.Y", float32(2.0), got)) + } - if got := monster.Testarrayofstring(0); !bytes.Equal([]byte("test1"), got) { - fail(FailString("Testarrayofstring(0)", "test1", got)) - } + if got := vec.Z(); float32(3.0) != got { + fail(FailString("Pos.Z", float32(3.0), got)) + } - if got := monster.Testarrayofstring(1); !bytes.Equal([]byte("test2"), got) { - fail(FailString("Testarrayofstring(1)", "test2", got)) + if got := vec.Test1(); float64(3.0) != got { + fail(FailString("Pos.Test1", float64(3.0), got)) + } + + if got := vec.Test2(); int8(2) != got { + fail(FailString("Pos.Test2", int8(2), got)) + } + + // initialize a Test from Test3(...) + t := new(example.Test) + t = vec.Test3(t) + if t == nil { + fail("vec.Test3(&t) failed") + } + + // check that new allocs equal given ones: + t2 := vec.Test3(nil) + if !reflect.DeepEqual(t, t2) { + fail("fresh allocation failed") + } + + // verify the properties of the Test + if got := t.A(); int16(5) != got { + fail(FailString("t.A()", int16(5), got)) + } + + if got := t.B(); int8(6) != got { + fail(FailString("t.B()", int8(6), got)) + } + + if got := monster.TestType(); example.AnyMonster != got { + fail(FailString("monster.TestType()", example.AnyMonster, got)) + } + + // initialize a Table from a union field Test(...) + var table2 flatbuffers.Table + if ok := monster.Test(&table2); !ok { + fail("monster.Test(&monster2) failed") + } + + // initialize a Monster from the Table from the union + var monster2 example.Monster + monster2.Init(table2.Bytes, table2.Pos) + + if got := monster2.Name(); !bytes.Equal([]byte("Fred"), got) { + fail(FailString("monster2.Name()", "Fred", got)) + } + + inventorySlice := monster.InventoryBytes() + if len(inventorySlice) != monster.InventoryLength() { + fail(FailString("len(monster.InventoryBytes) != monster.InventoryLength", len(inventorySlice), monster.InventoryLength())) + } + + if got := monster.InventoryLength(); 5 != got { + fail(FailString("monster.InventoryLength", 5, got)) + } + + invsum := 0 + l := monster.InventoryLength() + for i := 0; i < l; i++ { + v := monster.Inventory(i) + if v != inventorySlice[i] { + fail(FailString("monster inventory slice[i] != Inventory(i)", v, inventorySlice[i])) + } + invsum += int(v) + } + if invsum != 10 { + fail(FailString("monster inventory sum", 10, invsum)) + } + + if got := monster.Test4Length(); 2 != got { + fail(FailString("monster.Test4Length()", 2, got)) + } + + var test0 example.Test + ok := monster.Test4(&test0, 0) + if !ok { + fail(FailString("monster.Test4(&test0, 0)", true, ok)) + } + + var test1 example.Test + ok = monster.Test4(&test1, 1) + if !ok { + fail(FailString("monster.Test4(&test1, 1)", true, ok)) + } + + // the position of test0 and test1 are swapped in monsterdata_java_wire + // and monsterdata_test_wire, so ignore ordering + v0 := test0.A() + v1 := test0.B() + v2 := test1.A() + v3 := test1.B() + sum := int(v0) + int(v1) + int(v2) + int(v3) + + if 100 != sum { + fail(FailString("test0 and test1 sum", 100, sum)) + } + + if got := monster.TestarrayofstringLength(); 2 != got { + fail(FailString("Testarrayofstring length", 2, got)) + } + + if got := monster.Testarrayofstring(0); !bytes.Equal([]byte("test1"), got) { + fail(FailString("Testarrayofstring(0)", "test1", got)) + } + + if got := monster.Testarrayofstring(1); !bytes.Equal([]byte("test2"), got) { + fail(FailString("Testarrayofstring(1)", "test2", got)) + } } } @@ -1161,6 +1166,38 @@ func CheckGeneratedBuild(fail func(string, ...interface{})) ([]byte, flatbuffers return b.Bytes, b.Head() } +// CheckTableAccessors checks that the table accessors work as expected. +func CheckTableAccessors(fail func(string, ...interface{})) { + // test struct accessor + b := flatbuffers.NewBuilder(0) + pos := example.CreateVec3(b, 1.0, 2.0, 3.0, 3.0, 4, 5, 6) + b.Finish(pos) + vec3Bytes := b.FinishedBytes() + vec3 := &example.Vec3{} + flatbuffers.GetRootAs(vec3Bytes, 0, vec3) + + if bytes.Compare(vec3Bytes, vec3.Table().Bytes) != 0 { + fail("invalid vec3 table") + } + + // test table accessor + b = flatbuffers.NewBuilder(0) + str := b.CreateString("MyStat") + example.StatStart(b) + example.StatAddId(b, str) + example.StatAddVal(b, 12345678) + example.StatAddCount(b, 12345) + pos = example.StatEnd(b) + b.Finish(pos) + statBytes := b.FinishedBytes() + stat := &example.Stat{} + flatbuffers.GetRootAs(statBytes, 0, stat) + + if bytes.Compare(statBytes, stat.Table().Bytes) != 0 { + fail("invalid stat table") + } +} + // CheckVtableDeduplication verifies that vtables are deduplicated. func CheckVtableDeduplication(fail func(string, ...interface{})) { b := flatbuffers.NewBuilder(0)