• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15from . import number_types as N
16from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags)
17
18from . import encode
19from . import packer
20
21from . import compat
22from .compat import range_func
23from .compat import memoryview_type
24from .compat import import_numpy, NumpyRequiredForThisFeature
25
26np = import_numpy()
27## @file
28## @addtogroup flatbuffers_python_api
29## @{
30
31## @cond FLATBUFFERS_INTERNAL
32class OffsetArithmeticError(RuntimeError):
33    """
34    Error caused by an Offset arithmetic error. Probably caused by bad
35    writing of fields. This is considered an unreachable situation in
36    normal circumstances.
37    """
38    pass
39
40
41class IsNotNestedError(RuntimeError):
42    """
43    Error caused by using a Builder to write Object data when not inside
44    an Object.
45    """
46    pass
47
48
49class IsNestedError(RuntimeError):
50    """
51    Error caused by using a Builder to begin an Object when an Object is
52    already being built.
53    """
54    pass
55
56
57class StructIsNotInlineError(RuntimeError):
58    """
59    Error caused by using a Builder to write a Struct at a location that
60    is not the current Offset.
61    """
62    pass
63
64
65class BuilderSizeError(RuntimeError):
66    """
67    Error caused by causing a Builder to exceed the hardcoded limit of 2
68    gigabytes.
69    """
70    pass
71
72class BuilderNotFinishedError(RuntimeError):
73    """
74    Error caused by not calling `Finish` before calling `Output`.
75    """
76    pass
77
78
79# VtableMetadataFields is the count of metadata fields in each vtable.
80VtableMetadataFields = 2
81## @endcond
82
83class Builder(object):
84    """ A Builder is used to construct one or more FlatBuffers.
85
86    Typically, Builder objects will be used from code generated by the `flatc`
87    compiler.
88
89    A Builder constructs byte buffers in a last-first manner for simplicity and
90    performance during reading.
91
92    Internally, a Builder is a state machine for creating FlatBuffer objects.
93
94    It holds the following internal state:
95        - Bytes: an array of bytes.
96        - current_vtable: a list of integers.
97        - vtables: a list of vtable entries (i.e. a list of list of integers).
98
99    Attributes:
100      Bytes: The internal `bytearray` for the Builder.
101      finished: A boolean determining if the Builder has been finalized.
102    """
103
104    ## @cond FLATBUFFERS_INTENRAL
105    __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd",
106                 "vtables", "nested", "finished")
107
108    """Maximum buffer size constant, in bytes.
109
110    Builder will never allow it's buffer grow over this size.
111    Currently equals 2Gb.
112    """
113    MAX_BUFFER_SIZE = 2**31
114    ## @endcond
115
116    def __init__(self, initialSize):
117        """Initializes a Builder of size `initial_size`.
118
119        The internal buffer is grown as needed.
120        """
121
122        if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE):
123            msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes."
124            raise BuilderSizeError(msg)
125
126        self.Bytes = bytearray(initialSize)
127        ## @cond FLATBUFFERS_INTERNAL
128        self.current_vtable = None
129        self.head = UOffsetTFlags.py_type(initialSize)
130        self.minalign = 1
131        self.objectEnd = None
132        self.vtables = []
133        self.nested = False
134        ## @endcond
135        self.finished = False
136
137
138    def Output(self):
139        """Return the portion of the buffer that has been used for writing data.
140
141        This is the typical way to access the FlatBuffer data inside the
142        builder. If you try to access `Builder.Bytes` directly, you would need
143        to manually index it with `Head()`, since the buffer is constructed
144        backwards.
145
146        It raises BuilderNotFinishedError if the buffer has not been finished
147        with `Finish`.
148        """
149
150        if not self.finished:
151            raise BuilderNotFinishedError()
152
153        return self.Bytes[self.Head():]
154
155    ## @cond FLATBUFFERS_INTERNAL
156    def StartObject(self, numfields):
157        """StartObject initializes bookkeeping for writing a new object."""
158
159        self.assertNotNested()
160
161        # use 32-bit offsets so that arithmetic doesn't overflow.
162        self.current_vtable = [0 for _ in range_func(numfields)]
163        self.objectEnd = self.Offset()
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    def CreateNumpyVector(self, x):
446        """CreateNumpyVector writes a numpy array into the buffer."""
447
448        if np is None:
449            # Numpy is required for this feature
450            raise NumpyRequiredForThisFeature("Numpy was not found.")
451
452        if not isinstance(x, np.ndarray):
453            raise TypeError("non-numpy-ndarray passed to CreateNumpyVector")
454
455        if x.dtype.kind not in ['b', 'i', 'u', 'f']:
456            raise TypeError("numpy-ndarray holds elements of unsupported datatype")
457
458        if x.ndim > 1:
459            raise TypeError("multidimensional-ndarray passed to CreateNumpyVector")
460
461        self.StartVector(x.itemsize, x.size, x.dtype.alignment)
462
463        # Ensure little endian byte ordering
464        if x.dtype.str[0] == "<":
465            x_lend = x
466        else:
467            x_lend = x.byteswap(inplace=False)
468
469        # Calculate total length
470        l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size)
471        ## @cond FLATBUFFERS_INTERNAL
472        self.head = UOffsetTFlags.py_type(self.Head() - l)
473        ## @endcond
474
475        # tobytes ensures c_contiguous ordering
476        self.Bytes[self.Head():self.Head()+l] = x_lend.tobytes(order='C')
477
478        return self.EndVector(x.size)
479
480    ## @cond FLATBUFFERS_INTERNAL
481    def assertNested(self):
482        """
483        Check that we are in the process of building an object.
484        """
485
486        if not self.nested:
487            raise IsNotNestedError()
488
489    def assertNotNested(self):
490        """
491        Check that no other objects are being built while making this
492        object. If not, raise an exception.
493        """
494
495        if self.nested:
496            raise IsNestedError()
497
498    def assertStructIsInline(self, obj):
499        """
500        Structs are always stored inline, so need to be created right
501        where they are used. You'll get this error if you created it
502        elsewhere.
503        """
504
505        N.enforce_number(obj, N.UOffsetTFlags)
506        if obj != self.Offset():
507            msg = ("flatbuffers: Tried to write a Struct at an Offset that "
508                   "is different from the current Offset of the Builder.")
509            raise StructIsNotInlineError(msg)
510
511    def Slot(self, slotnum):
512        """
513        Slot sets the vtable key `voffset` to the current location in the
514        buffer.
515
516        """
517        self.assertNested()
518        self.current_vtable[slotnum] = self.Offset()
519    ## @endcond
520
521    def __Finish(self, rootTable, sizePrefix):
522        """Finish finalizes a buffer, pointing to the given `rootTable`."""
523        N.enforce_number(rootTable, N.UOffsetTFlags)
524        prepSize = N.UOffsetTFlags.bytewidth
525        if sizePrefix:
526            prepSize += N.Int32Flags.bytewidth
527        self.Prep(self.minalign, prepSize)
528        self.PrependUOffsetTRelative(rootTable)
529        if sizePrefix:
530            size = len(self.Bytes) - self.Head()
531            N.enforce_number(size, N.Int32Flags)
532            self.PrependInt32(size)
533        self.finished = True
534        return self.Head()
535
536    def Finish(self, rootTable):
537        """Finish finalizes a buffer, pointing to the given `rootTable`."""
538        return self.__Finish(rootTable, False)
539
540    def FinishSizePrefixed(self, rootTable):
541        """
542        Finish finalizes a buffer, pointing to the given `rootTable`,
543        with the size prefixed.
544        """
545        return self.__Finish(rootTable, True)
546
547    ## @cond FLATBUFFERS_INTERNAL
548    def Prepend(self, flags, off):
549        self.Prep(flags.bytewidth, 0)
550        self.Place(off, flags)
551
552    def PrependSlot(self, flags, o, x, d):
553        N.enforce_number(x, flags)
554        N.enforce_number(d, flags)
555        if x != d:
556            self.Prepend(flags, x)
557            self.Slot(o)
558
559    def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args)
560
561    def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
562
563    def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
564
565    def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args)
566
567    def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args)
568
569    def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args)
570
571    def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args)
572
573    def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args)
574
575    def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args)
576
577    def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args)
578
579    def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags,
580                                                          *args)
581
582    def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags,
583                                                          *args)
584
585    def PrependUOffsetTRelativeSlot(self, o, x, d):
586        """
587        PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at
588        vtable slot `o`. If value `x` equals default `d`, then the slot will
589        be set to zero and no other data will be written.
590        """
591
592        if x != d:
593            self.PrependUOffsetTRelative(x)
594            self.Slot(o)
595
596    def PrependStructSlot(self, v, x, d):
597        """
598        PrependStructSlot prepends a struct onto the object at vtable slot `o`.
599        Structs are stored inline, so nothing additional is being added.
600        In generated code, `d` is always 0.
601        """
602
603        N.enforce_number(d, N.UOffsetTFlags)
604        if x != d:
605            self.assertStructIsInline(x)
606            self.Slot(v)
607
608    ## @endcond
609
610    def PrependBool(self, x):
611        """Prepend a `bool` to the Builder buffer.
612
613        Note: aligns and checks for space.
614        """
615        self.Prepend(N.BoolFlags, x)
616
617    def PrependByte(self, x):
618        """Prepend a `byte` to the Builder buffer.
619
620        Note: aligns and checks for space.
621        """
622        self.Prepend(N.Uint8Flags, x)
623
624    def PrependUint8(self, x):
625        """Prepend an `uint8` to the Builder buffer.
626
627        Note: aligns and checks for space.
628        """
629        self.Prepend(N.Uint8Flags, x)
630
631    def PrependUint16(self, x):
632        """Prepend an `uint16` to the Builder buffer.
633
634        Note: aligns and checks for space.
635        """
636        self.Prepend(N.Uint16Flags, x)
637
638    def PrependUint32(self, x):
639        """Prepend an `uint32` to the Builder buffer.
640
641        Note: aligns and checks for space.
642        """
643        self.Prepend(N.Uint32Flags, x)
644
645    def PrependUint64(self, x):
646        """Prepend an `uint64` to the Builder buffer.
647
648        Note: aligns and checks for space.
649        """
650        self.Prepend(N.Uint64Flags, x)
651
652    def PrependInt8(self, x):
653        """Prepend an `int8` to the Builder buffer.
654
655        Note: aligns and checks for space.
656        """
657        self.Prepend(N.Int8Flags, x)
658
659    def PrependInt16(self, x):
660        """Prepend an `int16` to the Builder buffer.
661
662        Note: aligns and checks for space.
663        """
664        self.Prepend(N.Int16Flags, x)
665
666    def PrependInt32(self, x):
667        """Prepend an `int32` to the Builder buffer.
668
669        Note: aligns and checks for space.
670        """
671        self.Prepend(N.Int32Flags, x)
672
673    def PrependInt64(self, x):
674        """Prepend an `int64` to the Builder buffer.
675
676        Note: aligns and checks for space.
677        """
678        self.Prepend(N.Int64Flags, x)
679
680    def PrependFloat32(self, x):
681        """Prepend a `float32` to the Builder buffer.
682
683        Note: aligns and checks for space.
684        """
685        self.Prepend(N.Float32Flags, x)
686
687    def PrependFloat64(self, x):
688        """Prepend a `float64` to the Builder buffer.
689
690        Note: aligns and checks for space.
691        """
692        self.Prepend(N.Float64Flags, x)
693
694##############################################################
695
696    ## @cond FLATBUFFERS_INTERNAL
697    def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x)
698
699    def Place(self, x, flags):
700        """
701        Place prepends a value specified by `flags` to the Builder,
702        without checking for available space.
703        """
704
705        N.enforce_number(x, flags)
706        self.head = self.head - flags.bytewidth
707        encode.Write(flags.packer_type, self.Bytes, self.Head(), x)
708
709    def PlaceVOffsetT(self, x):
710        """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking
711        for space.
712        """
713        N.enforce_number(x, N.VOffsetTFlags)
714        self.head = self.head - N.VOffsetTFlags.bytewidth
715        encode.Write(packer.voffset, self.Bytes, self.Head(), x)
716
717    def PlaceSOffsetT(self, x):
718        """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking
719        for space.
720        """
721        N.enforce_number(x, N.SOffsetTFlags)
722        self.head = self.head - N.SOffsetTFlags.bytewidth
723        encode.Write(packer.soffset, self.Bytes, self.Head(), x)
724
725    def PlaceUOffsetT(self, x):
726        """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking
727        for space.
728        """
729        N.enforce_number(x, N.UOffsetTFlags)
730        self.head = self.head - N.UOffsetTFlags.bytewidth
731        encode.Write(packer.uoffset, self.Bytes, self.Head(), x)
732    ## @endcond
733
734## @cond FLATBUFFERS_INTERNAL
735def vtableEqual(a, objectStart, b):
736    """vtableEqual compares an unwritten vtable to a written vtable."""
737
738    N.enforce_number(objectStart, N.UOffsetTFlags)
739
740    if len(a) * N.VOffsetTFlags.bytewidth != len(b):
741        return False
742
743    for i, elem in enumerate(a):
744        x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth)
745
746        # Skip vtable entries that indicate a default value.
747        if x == 0 and elem == 0:
748            pass
749        else:
750            y = objectStart - elem
751            if x != y:
752                return False
753    return True
754## @endcond
755## @}
756