Merge pull request #343 from rw/go-builder-ergonomics
Go: Improve Builder user interface.
This commit is contained in:
commit
4725be015d
|
@ -6,14 +6,17 @@ package flatbuffers
|
|||
// A Builder constructs byte buffers in a last-first manner for simplicity and
|
||||
// performance.
|
||||
type Builder struct {
|
||||
// `Bytes` gives raw access to the buffer. Most users will want to use
|
||||
// FinishedBytes() instead.
|
||||
Bytes []byte
|
||||
|
||||
minalign int
|
||||
vtable []UOffsetT
|
||||
objectEnd UOffsetT
|
||||
insideObject bool
|
||||
vtables []UOffsetT
|
||||
head UOffsetT
|
||||
nested bool
|
||||
finished bool
|
||||
}
|
||||
|
||||
// NewBuilder initializes a Builder of size `initial_size`.
|
||||
|
@ -33,7 +36,7 @@ func NewBuilder(initialSize int) *Builder {
|
|||
}
|
||||
|
||||
// Reset truncates the underlying Builder buffer, facilitating alloc-free
|
||||
// reuse of a Builder.
|
||||
// reuse of a Builder. It also resets bookkeeping data.
|
||||
func (b *Builder) Reset() {
|
||||
if b.Bytes != nil {
|
||||
b.Bytes = b.Bytes[:cap(b.Bytes)]
|
||||
|
@ -49,12 +52,22 @@ func (b *Builder) Reset() {
|
|||
|
||||
b.head = UOffsetT(len(b.Bytes))
|
||||
b.minalign = 1
|
||||
b.nested = false
|
||||
b.finished = false
|
||||
}
|
||||
|
||||
// FinishedBytes returns a pointer to the written data in the byte buffer.
|
||||
// Panics if the builder is not in a finished state (which is caused by calling
|
||||
// `Finish()`).
|
||||
func (b *Builder) FinishedBytes() []byte {
|
||||
b.assertFinished()
|
||||
return b.Bytes[b.Head():]
|
||||
}
|
||||
|
||||
// StartObject initializes bookkeeping for writing a new object.
|
||||
func (b *Builder) StartObject(numfields int) {
|
||||
b.notNested()
|
||||
b.insideObject = true
|
||||
b.assertNotNested()
|
||||
b.nested = true
|
||||
|
||||
// use 32-bit offsets so that arithmetic doesn't overflow.
|
||||
if cap(b.vtable) < numfields || b.vtable == nil {
|
||||
|
@ -129,7 +142,7 @@ func (b *Builder) WriteVtable() (n UOffsetT) {
|
|||
var off UOffsetT
|
||||
if b.vtable[i] != 0 {
|
||||
// Forward reference to field;
|
||||
// use 32bit number to ensure no overflow:
|
||||
// use 32bit number to assert no overflow:
|
||||
off = objectOffset - b.vtable[i]
|
||||
}
|
||||
|
||||
|
@ -173,11 +186,9 @@ func (b *Builder) WriteVtable() (n UOffsetT) {
|
|||
|
||||
// EndObject writes data necessary to finish object construction.
|
||||
func (b *Builder) EndObject() UOffsetT {
|
||||
if !b.insideObject {
|
||||
panic("not in object")
|
||||
}
|
||||
b.assertNested()
|
||||
n := b.WriteVtable()
|
||||
b.insideObject = false
|
||||
b.nested = false
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -271,7 +282,8 @@ func (b *Builder) PrependUOffsetT(off UOffsetT) {
|
|||
// <UOffsetT: number of elements in this vector>
|
||||
// <T: data>+, where T is the type of elements of this vector.
|
||||
func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT {
|
||||
b.notNested()
|
||||
b.assertNotNested()
|
||||
b.nested = true
|
||||
b.Prep(SizeUint32, elemSize*numElems)
|
||||
b.Prep(alignment, elemSize*numElems) // Just in case alignment > int.
|
||||
return b.Offset()
|
||||
|
@ -279,14 +291,19 @@ func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT {
|
|||
|
||||
// EndVector writes data necessary to finish vector construction.
|
||||
func (b *Builder) EndVector(vectorNumElems int) UOffsetT {
|
||||
b.assertNested()
|
||||
|
||||
// we already made space for this, so write without PrependUint32
|
||||
b.PlaceUOffsetT(UOffsetT(vectorNumElems))
|
||||
|
||||
b.nested = false
|
||||
return b.Offset()
|
||||
}
|
||||
|
||||
// CreateString writes a null-terminated string as a vector.
|
||||
func (b *Builder) CreateString(s string) UOffsetT {
|
||||
b.notNested()
|
||||
b.assertNotNested()
|
||||
b.nested = true
|
||||
|
||||
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
|
||||
b.PlaceByte(0)
|
||||
|
@ -301,7 +318,8 @@ func (b *Builder) CreateString(s string) UOffsetT {
|
|||
|
||||
// CreateByteString writes a byte slice as a string (null-terminated).
|
||||
func (b *Builder) CreateByteString(s []byte) UOffsetT {
|
||||
b.notNested()
|
||||
b.assertNotNested()
|
||||
b.nested = true
|
||||
|
||||
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
|
||||
b.PlaceByte(0)
|
||||
|
@ -316,6 +334,9 @@ func (b *Builder) CreateByteString(s []byte) UOffsetT {
|
|||
|
||||
// CreateByteVector writes a ubyte vector
|
||||
func (b *Builder) CreateByteVector(v []byte) UOffsetT {
|
||||
b.assertNotNested()
|
||||
b.nested = true
|
||||
|
||||
b.Prep(int(SizeUOffsetT), len(v)*SizeByte)
|
||||
|
||||
l := UOffsetT(len(v))
|
||||
|
@ -326,20 +347,38 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT {
|
|||
return b.EndVector(len(v))
|
||||
}
|
||||
|
||||
func (b *Builder) notNested() {
|
||||
// Check that no other objects are being built while making this
|
||||
// object. If not, panic:
|
||||
if b.insideObject {
|
||||
panic("non-inline data write inside of object")
|
||||
func (b *Builder) assertNested() {
|
||||
// If you get this assert, you're in an object while trying to write
|
||||
// data that belongs outside of an object.
|
||||
// To fix this, write non-inline data (like vectors) before creating
|
||||
// objects.
|
||||
if !b.nested {
|
||||
panic("Incorrect creation order: must be inside object.")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) nested(obj UOffsetT) {
|
||||
// Structs are always stored inline, so need to be created right
|
||||
// where they are used. You'll get this panic if you created it
|
||||
// elsewhere:
|
||||
if obj != b.Offset() {
|
||||
panic("inline data write outside of object")
|
||||
func (b *Builder) assertNotNested() {
|
||||
// If you hit this, you're trying to construct a Table/Vector/String
|
||||
// during the construction of its parent table (between the MyTableBuilder
|
||||
// and builder.Finish()).
|
||||
// Move the creation of these sub-objects to above the MyTableBuilder to
|
||||
// not get this assert.
|
||||
// Ignoring this assert may appear to work in simple cases, but the reason
|
||||
// it is here is that storing objects in-line may cause vtable offsets
|
||||
// to not fit anymore. It also leads to vtable duplication.
|
||||
if b.nested {
|
||||
panic("Incorrect creation order: object must not be nested.")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) assertFinished() {
|
||||
// If you get this assert, you're attempting to get access a buffer
|
||||
// which hasn't been finished yet. Be sure to call builder.Finish()
|
||||
// with your root table.
|
||||
// If you really need to access an unfinished buffer, use the Bytes
|
||||
// buffer directly.
|
||||
if !b.finished {
|
||||
panic("Incorrect use of FinishedBytes(): must call 'Finish' first.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,7 +522,10 @@ func (b *Builder) PrependUOffsetTSlot(o int, x, d UOffsetT) {
|
|||
// In generated code, `d` is always 0.
|
||||
func (b *Builder) PrependStructSlot(voffset int, x, d UOffsetT) {
|
||||
if x != d {
|
||||
b.nested(x)
|
||||
b.assertNested()
|
||||
if x != b.Offset() {
|
||||
panic("inline data write outside of object")
|
||||
}
|
||||
b.Slot(voffset)
|
||||
}
|
||||
}
|
||||
|
@ -495,8 +537,10 @@ func (b *Builder) Slot(slotnum int) {
|
|||
|
||||
// Finish finalizes a buffer, pointing to the given `rootTable`.
|
||||
func (b *Builder) Finish(rootTable UOffsetT) {
|
||||
b.assertNotNested()
|
||||
b.Prep(b.minalign, SizeUOffsetT)
|
||||
b.PrependUOffsetT(rootTable)
|
||||
b.finished = true
|
||||
}
|
||||
|
||||
// vtableEqual compares an unwritten vtable to a written vtable.
|
||||
|
|
|
@ -70,10 +70,10 @@ func TestAll(t *testing.T) {
|
|||
|
||||
// Verify that panics are raised during exceptional conditions:
|
||||
CheckNotInObjectError(t.Fatalf)
|
||||
CheckObjectIsNestedError(t.Fatalf)
|
||||
CheckStringIsNestedError(t.Fatalf)
|
||||
CheckByteStringIsNestedError(t.Fatalf)
|
||||
CheckStructIsNotInlineError(t.Fatalf)
|
||||
CheckFinishedBytesError(t.Fatalf)
|
||||
|
||||
// Verify that using the generated Go code builds a buffer without
|
||||
// returning errors:
|
||||
|
@ -1113,20 +1113,6 @@ func CheckNotInObjectError(fail func(string, ...interface{})) {
|
|||
b.EndObject()
|
||||
}
|
||||
|
||||
// CheckObjectIsNestedError verifies that an object can not be created inside
|
||||
// another object.
|
||||
func CheckObjectIsNestedError(fail func(string, ...interface{})) {
|
||||
b := flatbuffers.NewBuilder(0)
|
||||
b.StartObject(0)
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
fail("expected panic in CheckObjectIsNestedError")
|
||||
}
|
||||
}()
|
||||
b.StartObject(0)
|
||||
}
|
||||
|
||||
// CheckStringIsNestedError verifies that a string can not be created inside
|
||||
// another object.
|
||||
func CheckStringIsNestedError(fail func(string, ...interface{})) {
|
||||
|
@ -1169,6 +1155,20 @@ func CheckStructIsNotInlineError(fail func(string, ...interface{})) {
|
|||
b.PrependStructSlot(0, 1, 0)
|
||||
}
|
||||
|
||||
// CheckFinishedBytesError verifies that `FinishedBytes` panics if the table
|
||||
// is not finished.
|
||||
func CheckFinishedBytesError(fail func(string, ...interface{})) {
|
||||
b := flatbuffers.NewBuilder(0)
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
fail("expected panic in CheckFinishedBytesError")
|
||||
}
|
||||
}()
|
||||
b.FinishedBytes()
|
||||
}
|
||||
|
||||
// CheckDocExample checks that the code given in FlatBuffers documentation
|
||||
// is syntactically correct.
|
||||
func CheckDocExample(buf []byte, off flatbuffers.UOffsetT, fail func(string, ...interface{})) {
|
||||
|
|
Loading…
Reference in New Issue