Home | History | Annotate | Download | only in flatbuffers
      1 # Copyright 2014 Google Inc. All rights reserved.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 from . import number_types as N
     16 from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags)
     17 
     18 from . import encode
     19 from . import packer
     20 
     21 from . import compat
     22 from .compat import range_func
     23 from .compat import memoryview_type
     24 
     25 
     26 ## @file
     27 ## @addtogroup flatbuffers_python_api
     28 ## @{
     29 
     30 ## @cond FLATBUFFERS_INTERNAL
     31 class OffsetArithmeticError(RuntimeError):
     32     """
     33     Error caused by an Offset arithmetic error. Probably caused by bad
     34     writing of fields. This is considered an unreachable situation in
     35     normal circumstances.
     36     """
     37     pass
     38 
     39 
     40 class IsNotNestedError(RuntimeError):
     41     """
     42     Error caused by using a Builder to write Object data when not inside
     43     an Object.
     44     """
     45     pass
     46 
     47 
     48 class IsNestedError(RuntimeError):
     49     """
     50     Error caused by using a Builder to begin an Object when an Object is
     51     already being built.
     52     """
     53     pass
     54 
     55 
     56 class StructIsNotInlineError(RuntimeError):
     57     """
     58     Error caused by using a Builder to write a Struct at a location that
     59     is not the current Offset.
     60     """
     61     pass
     62 
     63 
     64 class BuilderSizeError(RuntimeError):
     65     """
     66     Error caused by causing a Builder to exceed the hardcoded limit of 2
     67     gigabytes.
     68     """
     69     pass
     70 
     71 class BuilderNotFinishedError(RuntimeError):
     72     """
     73     Error caused by not calling `Finish` before calling `Output`.
     74     """
     75     pass
     76 
     77 
     78 # VtableMetadataFields is the count of metadata fields in each vtable.
     79 VtableMetadataFields = 2
     80 ## @endcond
     81 
     82 class Builder(object):
     83     """ A Builder is used to construct one or more FlatBuffers.
     84 
     85     Typically, Builder objects will be used from code generated by the `flatc`
     86     compiler.
     87 
     88     A Builder constructs byte buffers in a last-first manner for simplicity and
     89     performance during reading.
     90 
     91     Internally, a Builder is a state machine for creating FlatBuffer objects.
     92 
     93     It holds the following internal state:
     94         - Bytes: an array of bytes.
     95         - current_vtable: a list of integers.
     96         - vtables: a list of vtable entries (i.e. a list of list of integers).
     97 
     98     Attributes:
     99       Bytes: The internal `bytearray` for the Builder.
    100       finished: A boolean determining if the Builder has been finalized.
    101     """
    102 
    103     ## @cond FLATBUFFERS_INTENRAL
    104     __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd",
    105                  "vtables", "nested", "finished")
    106 
    107     """Maximum buffer size constant, in bytes.
    108 
    109     Builder will never allow it's buffer grow over this size.
    110     Currently equals 2Gb.
    111     """
    112     MAX_BUFFER_SIZE = 2**31
    113     ## @endcond
    114 
    115     def __init__(self, initialSize):
    116         """Initializes a Builder of size `initial_size`.
    117 
    118         The internal buffer is grown as needed.
    119         """
    120 
    121         if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE):
    122             msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes."
    123             raise BuilderSizeError(msg)
    124 
    125         self.Bytes = bytearray(initialSize)
    126         ## @cond FLATBUFFERS_INTERNAL
    127         self.current_vtable = None
    128         self.head = UOffsetTFlags.py_type(initialSize)
    129         self.minalign = 1
    130         self.objectEnd = None
    131         self.vtables = []
    132         self.nested = False
    133         ## @endcond
    134         self.finished = False
    135 
    136 
    137     def Output(self):
    138         """Return the portion of the buffer that has been used for writing data.
    139 
    140         This is the typical way to access the FlatBuffer data inside the
    141         builder. If you try to access `Builder.Bytes` directly, you would need
    142         to manually index it with `Head()`, since the buffer is constructed
    143         backwards.
    144 
    145         It raises BuilderNotFinishedError if the buffer has not been finished
    146         with `Finish`.
    147         """
    148 
    149         if not self.finished:
    150             raise BuilderNotFinishedError()
    151 
    152         return self.Bytes[self.Head():]
    153 
    154     ## @cond FLATBUFFERS_INTERNAL
    155     def StartObject(self, numfields):
    156         """StartObject initializes bookkeeping for writing a new object."""
    157 
    158         self.assertNotNested()
    159 
    160         # use 32-bit offsets so that arithmetic doesn't overflow.
    161         self.current_vtable = [0 for _ in range_func(numfields)]
    162         self.objectEnd = self.Offset()
    163         self.minalign = 1
    164         self.nested = True
    165 
    166     def WriteVtable(self):
    167         """
    168         WriteVtable serializes the vtable for the current object, if needed.
    169 
    170         Before writing out the vtable, this checks pre-existing vtables for
    171         equality to this one. If an equal vtable is found, point the object to
    172         the existing vtable and return.
    173 
    174         Because vtable values are sensitive to alignment of object data, not
    175         all logically-equal vtables will be deduplicated.
    176 
    177         A vtable has the following format:
    178           <VOffsetT: size of the vtable in bytes, including this value>
    179           <VOffsetT: size of the object in bytes, including the vtable offset>
    180           <VOffsetT: offset for a field> * N, where N is the number of fields
    181                      in the schema for this type. Includes deprecated fields.
    182         Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide.
    183 
    184         An object has the following format:
    185           <SOffsetT: offset to this object's vtable (may be negative)>
    186           <byte: data>+
    187         """
    188 
    189         # Prepend a zero scalar to the object. Later in this function we'll
    190         # write an offset here that points to the object's vtable:
    191         self.PrependSOffsetTRelative(0)
    192 
    193         objectOffset = self.Offset()
    194         existingVtable = None
    195 
    196         # Trim trailing 0 offsets.
    197         while self.current_vtable and self.current_vtable[-1] == 0:
    198             self.current_vtable.pop()
    199 
    200         # Search backwards through existing vtables, because similar vtables
    201         # are likely to have been recently appended. See
    202         # BenchmarkVtableDeduplication for a case in which this heuristic
    203         # saves about 30% of the time used in writing objects with duplicate
    204         # tables.
    205 
    206         i = len(self.vtables) - 1
    207         while i >= 0:
    208             # Find the other vtable, which is associated with `i`:
    209             vt2Offset = self.vtables[i]
    210             vt2Start = len(self.Bytes) - vt2Offset
    211             vt2Len = encode.Get(packer.voffset, self.Bytes, vt2Start)
    212 
    213             metadata = VtableMetadataFields * N.VOffsetTFlags.bytewidth
    214             vt2End = vt2Start + vt2Len
    215             vt2 = self.Bytes[vt2Start+metadata:vt2End]
    216 
    217             # Compare the other vtable to the one under consideration.
    218             # If they are equal, store the offset and break:
    219             if vtableEqual(self.current_vtable, objectOffset, vt2):
    220                 existingVtable = vt2Offset
    221                 break
    222 
    223             i -= 1
    224 
    225         if existingVtable is None:
    226             # Did not find a vtable, so write this one to the buffer.
    227 
    228             # Write out the current vtable in reverse , because
    229             # serialization occurs in last-first order:
    230             i = len(self.current_vtable) - 1
    231             while i >= 0:
    232                 off = 0
    233                 if self.current_vtable[i] != 0:
    234                     # Forward reference to field;
    235                     # use 32bit number to ensure no overflow:
    236                     off = objectOffset - self.current_vtable[i]
    237 
    238                 self.PrependVOffsetT(off)
    239                 i -= 1
    240 
    241             # The two metadata fields are written last.
    242 
    243             # First, store the object bytesize:
    244             objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd)
    245             self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize))
    246 
    247             # Second, store the vtable bytesize:
    248             vBytes = len(self.current_vtable) + VtableMetadataFields
    249             vBytes *= N.VOffsetTFlags.bytewidth
    250             self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes))
    251 
    252             # Next, write the offset to the new vtable in the
    253             # already-allocated SOffsetT at the beginning of this object:
    254             objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
    255             encode.Write(packer.soffset, self.Bytes, objectStart,
    256                          SOffsetTFlags.py_type(self.Offset() - objectOffset))
    257 
    258             # Finally, store this vtable in memory for future
    259             # deduplication:
    260             self.vtables.append(self.Offset())
    261         else:
    262             # Found a duplicate vtable.
    263 
    264             objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
    265             self.head = UOffsetTFlags.py_type(objectStart)
    266 
    267             # Write the offset to the found vtable in the
    268             # already-allocated SOffsetT at the beginning of this object:
    269             encode.Write(packer.soffset, self.Bytes, self.Head(),
    270                          SOffsetTFlags.py_type(existingVtable - objectOffset))
    271 
    272         self.current_vtable = None
    273         return objectOffset
    274 
    275     def EndObject(self):
    276         """EndObject writes data necessary to finish object construction."""
    277         self.assertNested()
    278         self.nested = False
    279         return self.WriteVtable()
    280 
    281     def growByteBuffer(self):
    282         """Doubles the size of the byteslice, and copies the old data towards
    283            the end of the new buffer (since we build the buffer backwards)."""
    284         if len(self.Bytes) == Builder.MAX_BUFFER_SIZE:
    285             msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes"
    286             raise BuilderSizeError(msg)
    287 
    288         newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE)
    289         if newSize == 0:
    290             newSize = 1
    291         bytes2 = bytearray(newSize)
    292         bytes2[newSize-len(self.Bytes):] = self.Bytes
    293         self.Bytes = bytes2
    294     ## @endcond
    295 
    296     def Head(self):
    297         """Get the start of useful data in the underlying byte buffer.
    298 
    299         Note: unlike other functions, this value is interpreted as from the
    300         left.
    301         """
    302         ## @cond FLATBUFFERS_INTERNAL
    303         return self.head
    304         ## @endcond
    305 
    306     ## @cond FLATBUFFERS_INTERNAL
    307     def Offset(self):
    308         """Offset relative to the end of the buffer."""
    309         return UOffsetTFlags.py_type(len(self.Bytes) - self.Head())
    310 
    311     def Pad(self, n):
    312         """Pad places zeros at the current offset."""
    313         for i in range_func(n):
    314             self.Place(0, N.Uint8Flags)
    315 
    316     def Prep(self, size, additionalBytes):
    317         """
    318         Prep prepares to write an element of `size` after `additional_bytes`
    319         have been written, e.g. if you write a string, you need to align
    320         such the int length field is aligned to SizeInt32, and the string
    321         data follows it directly.
    322         If all you need to do is align, `additionalBytes` will be 0.
    323         """
    324 
    325         # Track the biggest thing we've ever aligned to.
    326         if size > self.minalign:
    327             self.minalign = size
    328 
    329         # Find the amount of alignment needed such that `size` is properly
    330         # aligned after `additionalBytes`:
    331         alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1
    332         alignSize &= (size - 1)
    333 
    334         # Reallocate the buffer if needed:
    335         while self.Head() < alignSize+size+additionalBytes:
    336             oldBufSize = len(self.Bytes)
    337             self.growByteBuffer()
    338             updated_head = self.head + len(self.Bytes) - oldBufSize
    339             self.head = UOffsetTFlags.py_type(updated_head)
    340         self.Pad(alignSize)
    341 
    342     def PrependSOffsetTRelative(self, off):
    343         """
    344         PrependSOffsetTRelative prepends an SOffsetT, relative to where it
    345         will be written.
    346         """
    347 
    348         # Ensure alignment is already done:
    349         self.Prep(N.SOffsetTFlags.bytewidth, 0)
    350         if not (off <= self.Offset()):
    351             msg = "flatbuffers: Offset arithmetic error."
    352             raise OffsetArithmeticError(msg)
    353         off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth
    354         self.PlaceSOffsetT(off2)
    355     ## @endcond
    356 
    357     def PrependUOffsetTRelative(self, off):
    358         """Prepends an unsigned offset into vector data, relative to where it
    359         will be written.
    360         """
    361 
    362         # Ensure alignment is already done:
    363         self.Prep(N.UOffsetTFlags.bytewidth, 0)
    364         if not (off <= self.Offset()):
    365             msg = "flatbuffers: Offset arithmetic error."
    366             raise OffsetArithmeticError(msg)
    367         off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth
    368         self.PlaceUOffsetT(off2)
    369 
    370     ## @cond FLATBUFFERS_INTERNAL
    371     def StartVector(self, elemSize, numElems, alignment):
    372         """
    373         StartVector initializes bookkeeping for writing a new vector.
    374 
    375         A vector has the following format:
    376           - <UOffsetT: number of elements in this vector>
    377           - <T: data>+, where T is the type of elements of this vector.
    378         """
    379 
    380         self.assertNotNested()
    381         self.nested = True
    382         self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems)
    383         self.Prep(alignment, elemSize*numElems)  # In case alignment > int.
    384         return self.Offset()
    385     ## @endcond
    386 
    387     def EndVector(self, vectorNumElems):
    388         """EndVector writes data necessary to finish vector construction."""
    389 
    390         self.assertNested()
    391         ## @cond FLATBUFFERS_INTERNAL
    392         self.nested = False
    393         ## @endcond
    394         # we already made space for this, so write without PrependUint32
    395         self.PlaceUOffsetT(vectorNumElems)
    396         return self.Offset()
    397 
    398     def CreateString(self, s, encoding='utf-8', errors='strict'):
    399         """CreateString writes a null-terminated byte string as a vector."""
    400 
    401         self.assertNotNested()
    402         ## @cond FLATBUFFERS_INTERNAL
    403         self.nested = True
    404         ## @endcond
    405 
    406         if isinstance(s, compat.string_types):
    407             x = s.encode(encoding, errors)
    408         elif isinstance(s, compat.binary_types):
    409             x = s
    410         else:
    411             raise TypeError("non-string passed to CreateString")
    412 
    413         self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth)
    414         self.Place(0, N.Uint8Flags)
    415 
    416         l = UOffsetTFlags.py_type(len(s))
    417         ## @cond FLATBUFFERS_INTERNAL
    418         self.head = UOffsetTFlags.py_type(self.Head() - l)
    419         ## @endcond
    420         self.Bytes[self.Head():self.Head()+l] = x
    421 
    422         return self.EndVector(len(x))
    423 
    424     def CreateByteVector(self, x):
    425         """CreateString writes a byte vector."""
    426 
    427         self.assertNotNested()
    428         ## @cond FLATBUFFERS_INTERNAL
    429         self.nested = True
    430         ## @endcond
    431 
    432         if not isinstance(x, compat.binary_types):
    433             raise TypeError("non-byte vector passed to CreateByteVector")
    434 
    435         self.Prep(N.UOffsetTFlags.bytewidth, len(x)*N.Uint8Flags.bytewidth)
    436 
    437         l = UOffsetTFlags.py_type(len(x))
    438         ## @cond FLATBUFFERS_INTERNAL
    439         self.head = UOffsetTFlags.py_type(self.Head() - l)
    440         ## @endcond
    441         self.Bytes[self.Head():self.Head()+l] = x
    442 
    443         return self.EndVector(len(x))
    444 
    445     ## @cond FLATBUFFERS_INTERNAL
    446     def assertNested(self):
    447         """
    448         Check that we are in the process of building an object.
    449         """
    450 
    451         if not self.nested:
    452             raise IsNotNestedError()
    453 
    454     def assertNotNested(self):
    455         """
    456         Check that no other objects are being built while making this
    457         object. If not, raise an exception.
    458         """
    459 
    460         if self.nested:
    461             raise IsNestedError()
    462 
    463     def assertStructIsInline(self, obj):
    464         """
    465         Structs are always stored inline, so need to be created right
    466         where they are used. You'll get this error if you created it
    467         elsewhere.
    468         """
    469 
    470         N.enforce_number(obj, N.UOffsetTFlags)
    471         if obj != self.Offset():
    472             msg = ("flatbuffers: Tried to write a Struct at an Offset that "
    473                    "is different from the current Offset of the Builder.")
    474             raise StructIsNotInlineError(msg)
    475 
    476     def Slot(self, slotnum):
    477         """
    478         Slot sets the vtable key `voffset` to the current location in the
    479         buffer.
    480 
    481         """
    482         self.assertNested()
    483         self.current_vtable[slotnum] = self.Offset()
    484     ## @endcond
    485 
    486     def Finish(self, rootTable):
    487         """Finish finalizes a buffer, pointing to the given `rootTable`."""
    488         N.enforce_number(rootTable, N.UOffsetTFlags)
    489         self.Prep(self.minalign, N.UOffsetTFlags.bytewidth)
    490         self.PrependUOffsetTRelative(rootTable)
    491         self.finished = True
    492         return self.Head()
    493 
    494     ## @cond FLATBUFFERS_INTERNAL
    495     def Prepend(self, flags, off):
    496         self.Prep(flags.bytewidth, 0)
    497         self.Place(off, flags)
    498 
    499     def PrependSlot(self, flags, o, x, d):
    500         N.enforce_number(x, flags)
    501         N.enforce_number(d, flags)
    502         if x != d:
    503             self.Prepend(flags, x)
    504             self.Slot(o)
    505 
    506     def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args)
    507 
    508     def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
    509 
    510     def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
    511 
    512     def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args)
    513 
    514     def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args)
    515 
    516     def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args)
    517 
    518     def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args)
    519 
    520     def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args)
    521 
    522     def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args)
    523 
    524     def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args)
    525 
    526     def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags,
    527                                                           *args)
    528 
    529     def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags,
    530                                                           *args)
    531 
    532     def PrependUOffsetTRelativeSlot(self, o, x, d):
    533         """
    534         PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at
    535         vtable slot `o`. If value `x` equals default `d`, then the slot will
    536         be set to zero and no other data will be written.
    537         """
    538 
    539         if x != d:
    540             self.PrependUOffsetTRelative(x)
    541             self.Slot(o)
    542 
    543     def PrependStructSlot(self, v, x, d):
    544         """
    545         PrependStructSlot prepends a struct onto the object at vtable slot `o`.
    546         Structs are stored inline, so nothing additional is being added.
    547         In generated code, `d` is always 0.
    548         """
    549 
    550         N.enforce_number(d, N.UOffsetTFlags)
    551         if x != d:
    552             self.assertStructIsInline(x)
    553             self.Slot(v)
    554 
    555     ## @endcond
    556 
    557     def PrependBool(self, x):
    558         """Prepend a `bool` to the Builder buffer.
    559 
    560         Note: aligns and checks for space.
    561         """
    562         self.Prepend(N.BoolFlags, x)
    563 
    564     def PrependByte(self, x):
    565         """Prepend a `byte` to the Builder buffer.
    566 
    567         Note: aligns and checks for space.
    568         """
    569         self.Prepend(N.Uint8Flags, x)
    570 
    571     def PrependUint8(self, x):
    572         """Prepend an `uint8` to the Builder buffer.
    573 
    574         Note: aligns and checks for space.
    575         """
    576         self.Prepend(N.Uint8Flags, x)
    577 
    578     def PrependUint16(self, x):
    579         """Prepend an `uint16` to the Builder buffer.
    580 
    581         Note: aligns and checks for space.
    582         """
    583         self.Prepend(N.Uint16Flags, x)
    584 
    585     def PrependUint32(self, x):
    586         """Prepend an `uint32` to the Builder buffer.
    587 
    588         Note: aligns and checks for space.
    589         """
    590         self.Prepend(N.Uint32Flags, x)
    591 
    592     def PrependUint64(self, x):
    593         """Prepend an `uint64` to the Builder buffer.
    594 
    595         Note: aligns and checks for space.
    596         """
    597         self.Prepend(N.Uint64Flags, x)
    598 
    599     def PrependInt8(self, x):
    600         """Prepend an `int8` to the Builder buffer.
    601 
    602         Note: aligns and checks for space.
    603         """
    604         self.Prepend(N.Int8Flags, x)
    605 
    606     def PrependInt16(self, x):
    607         """Prepend an `int16` to the Builder buffer.
    608 
    609         Note: aligns and checks for space.
    610         """
    611         self.Prepend(N.Int16Flags, x)
    612 
    613     def PrependInt32(self, x):
    614         """Prepend an `int32` to the Builder buffer.
    615 
    616         Note: aligns and checks for space.
    617         """
    618         self.Prepend(N.Int32Flags, x)
    619 
    620     def PrependInt64(self, x):
    621         """Prepend an `int64` to the Builder buffer.
    622 
    623         Note: aligns and checks for space.
    624         """
    625         self.Prepend(N.Int64Flags, x)
    626 
    627     def PrependFloat32(self, x):
    628         """Prepend a `float32` to the Builder buffer.
    629 
    630         Note: aligns and checks for space.
    631         """
    632         self.Prepend(N.Float32Flags, x)
    633 
    634     def PrependFloat64(self, x):
    635         """Prepend a `float64` to the Builder buffer.
    636 
    637         Note: aligns and checks for space.
    638         """
    639         self.Prepend(N.Float64Flags, x)
    640 
    641 ##############################################################
    642 
    643     ## @cond FLATBUFFERS_INTERNAL
    644     def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x)
    645 
    646     def Place(self, x, flags):
    647         """
    648         Place prepends a value specified by `flags` to the Builder,
    649         without checking for available space.
    650         """
    651 
    652         N.enforce_number(x, flags)
    653         self.head = self.head - flags.bytewidth
    654         encode.Write(flags.packer_type, self.Bytes, self.Head(), x)
    655 
    656     def PlaceVOffsetT(self, x):
    657         """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking
    658         for space.
    659         """
    660         N.enforce_number(x, N.VOffsetTFlags)
    661         self.head = self.head - N.VOffsetTFlags.bytewidth
    662         encode.Write(packer.voffset, self.Bytes, self.Head(), x)
    663 
    664     def PlaceSOffsetT(self, x):
    665         """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking
    666         for space.
    667         """
    668         N.enforce_number(x, N.SOffsetTFlags)
    669         self.head = self.head - N.SOffsetTFlags.bytewidth
    670         encode.Write(packer.soffset, self.Bytes, self.Head(), x)
    671 
    672     def PlaceUOffsetT(self, x):
    673         """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking
    674         for space.
    675         """
    676         N.enforce_number(x, N.UOffsetTFlags)
    677         self.head = self.head - N.UOffsetTFlags.bytewidth
    678         encode.Write(packer.uoffset, self.Bytes, self.Head(), x)
    679     ## @endcond
    680 
    681 ## @cond FLATBUFFERS_INTERNAL
    682 def vtableEqual(a, objectStart, b):
    683     """vtableEqual compares an unwritten vtable to a written vtable."""
    684 
    685     N.enforce_number(objectStart, N.UOffsetTFlags)
    686 
    687     if len(a) * N.VOffsetTFlags.bytewidth != len(b):
    688         return False
    689 
    690     for i, elem in enumerate(a):
    691         x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth)
    692 
    693         # Skip vtable entries that indicate a default value.
    694         if x == 0 and elem == 0:
    695             pass
    696         else:
    697             y = objectStart - elem
    698             if x != y:
    699                 return False
    700     return True
    701 ## @endcond
    702 ## @}
    703