...

Text file src/github.com/google/flatbuffers/ts/builder.ts

Documentation: github.com/google/flatbuffers/ts

     1import { ByteBuffer } from "./byte-buffer.js"
     2import { SIZEOF_SHORT, SIZE_PREFIX_LENGTH, SIZEOF_INT, FILE_IDENTIFIER_LENGTH } from "./constants.js"
     3import { Offset, IGeneratedObject } from "./types.js"
     4
     5export class Builder {
     6    private bb: ByteBuffer
     7    /** Remaining space in the ByteBuffer. */
     8    private space: number
     9    /** Minimum alignment encountered so far. */
    10    private minalign = 1
    11    /** The vtable for the current table. */
    12    private vtable: number[] | null = null
    13    /** The amount of fields we're actually using. */
    14    private vtable_in_use = 0
    15    /** Whether we are currently serializing a table. */
    16    private isNested = false;
    17    /** Starting offset of the current struct/table. */
    18    private object_start = 0
    19    /** List of offsets of all vtables. */
    20    private vtables: number[] = []
    21    /** For the current vector being built. */
    22    private vector_num_elems = 0 
    23    /** False omits default values from the serialized data */
    24    private force_defaults = false;
    25    
    26    private string_maps: Map<string | Uint8Array, number> | null = null;
    27    private text_encoder = new TextEncoder();
    28  
    29    /**
    30     * Create a FlatBufferBuilder.
    31     */
    32    constructor(opt_initial_size?: number) {
    33      let initial_size: number;
    34  
    35      if (!opt_initial_size) {
    36        initial_size = 1024;
    37      } else {
    38        initial_size = opt_initial_size;
    39      }
    40  
    41      /**
    42       * @type {ByteBuffer}
    43       * @private
    44       */
    45      this.bb = ByteBuffer.allocate(initial_size);
    46      this.space = initial_size;
    47    }
    48  
    49  
    50    clear(): void {
    51      this.bb.clear();
    52      this.space = this.bb.capacity();
    53      this.minalign = 1;
    54      this.vtable = null;
    55      this.vtable_in_use = 0;
    56      this.isNested = false;
    57      this.object_start = 0;
    58      this.vtables = [];
    59      this.vector_num_elems = 0;
    60      this.force_defaults = false;
    61      this.string_maps = null;
    62    }
    63  
    64    /**
    65     * In order to save space, fields that are set to their default value
    66     * don't get serialized into the buffer. Forcing defaults provides a
    67     * way to manually disable this optimization.
    68     *
    69     * @param forceDefaults true always serializes default values
    70     */
    71    forceDefaults(forceDefaults: boolean): void {
    72      this.force_defaults = forceDefaults;
    73    }
    74  
    75    /**
    76     * Get the ByteBuffer representing the FlatBuffer. Only call this after you've
    77     * called finish(). The actual data starts at the ByteBuffer's current position,
    78     * not necessarily at 0.
    79     */
    80    dataBuffer(): ByteBuffer {
    81      return this.bb;
    82    }
    83  
    84    /**
    85     * Get the bytes representing the FlatBuffer. Only call this after you've
    86     * called finish().
    87     */
    88    asUint8Array(): Uint8Array {
    89      return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset());
    90    }
    91  
    92    /**
    93     * Prepare to write an element of `size` after `additional_bytes` have been
    94     * written, e.g. if you write a string, you need to align such the int length
    95     * field is aligned to 4 bytes, and the string data follows it directly. If all
    96     * you need to do is alignment, `additional_bytes` will be 0.
    97     *
    98     * @param size This is the of the new element to write
    99     * @param additional_bytes The padding size
   100     */
   101    prep(size: number, additional_bytes: number): void {
   102      // Track the biggest thing we've ever aligned to.
   103      if (size > this.minalign) {
   104        this.minalign = size;
   105      }
   106  
   107      // Find the amount of alignment needed such that `size` is properly
   108      // aligned after `additional_bytes`
   109      const align_size = ((~(this.bb.capacity() - this.space + additional_bytes)) + 1) & (size - 1);
   110  
   111      // Reallocate the buffer if needed.
   112      while (this.space < align_size + size + additional_bytes) {
   113        const old_buf_size = this.bb.capacity();
   114        this.bb = Builder.growByteBuffer(this.bb);
   115        this.space += this.bb.capacity() - old_buf_size;
   116      }
   117  
   118      this.pad(align_size);
   119    }
   120  
   121    pad(byte_size: number): void {
   122      for (let i = 0; i < byte_size; i++) {
   123        this.bb.writeInt8(--this.space, 0);
   124      }
   125    }
   126  
   127    writeInt8(value: number): void {
   128      this.bb.writeInt8(this.space -= 1, value);
   129    }
   130  
   131    writeInt16(value: number): void {
   132      this.bb.writeInt16(this.space -= 2, value);
   133    }
   134  
   135    writeInt32(value: number): void {
   136      this.bb.writeInt32(this.space -= 4, value);
   137    }
   138  
   139    writeInt64(value: bigint): void {
   140      this.bb.writeInt64(this.space -= 8, value);
   141    }
   142  
   143    writeFloat32(value: number): void {
   144      this.bb.writeFloat32(this.space -= 4, value);
   145    }
   146  
   147    writeFloat64(value: number): void {
   148      this.bb.writeFloat64(this.space -= 8, value);
   149    }
   150  
   151    /**
   152     * Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary).
   153     * @param value The `int8` to add the buffer.
   154     */
   155    addInt8(value: number): void {
   156      this.prep(1, 0);
   157      this.writeInt8(value);
   158    }
   159  
   160    /**
   161     * Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary).
   162     * @param value The `int16` to add the buffer.
   163     */
   164    addInt16(value: number): void {
   165      this.prep(2, 0);
   166      this.writeInt16(value);
   167    }
   168  
   169    /**
   170     * Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary).
   171     * @param value The `int32` to add the buffer.
   172     */
   173    addInt32(value: number): void {
   174      this.prep(4, 0);
   175      this.writeInt32(value);
   176    }
   177  
   178    /**
   179     * Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary).
   180     * @param value The `int64` to add the buffer.
   181     */
   182    addInt64(value: bigint): void {
   183      this.prep(8, 0);
   184      this.writeInt64(value);
   185    }
   186  
   187    /**
   188     * Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary).
   189     * @param value The `float32` to add the buffer.
   190     */
   191    addFloat32(value: number): void {
   192      this.prep(4, 0);
   193      this.writeFloat32(value);
   194    }
   195  
   196    /**
   197     * Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary).
   198     * @param value The `float64` to add the buffer.
   199     */
   200    addFloat64(value: number): void {
   201      this.prep(8, 0);
   202      this.writeFloat64(value);
   203    }
   204  
   205    addFieldInt8(voffset: number, value: number, defaultValue: number): void {
   206      if (this.force_defaults || value != defaultValue) {
   207        this.addInt8(value);
   208        this.slot(voffset);
   209      }
   210    }
   211  
   212    addFieldInt16(voffset: number, value: number, defaultValue: number): void {
   213      if (this.force_defaults || value != defaultValue) {
   214        this.addInt16(value);
   215        this.slot(voffset);
   216      }
   217    }
   218  
   219    addFieldInt32(voffset: number, value: number, defaultValue: number): void {
   220      if (this.force_defaults || value != defaultValue) {
   221        this.addInt32(value);
   222        this.slot(voffset);
   223      }
   224    }
   225  
   226    addFieldInt64(voffset: number, value: bigint, defaultValue: bigint): void {
   227      if (this.force_defaults || value !== defaultValue) {
   228        this.addInt64(value);
   229        this.slot(voffset);
   230      }
   231    }
   232  
   233    addFieldFloat32(voffset: number, value: number, defaultValue: number): void {
   234      if (this.force_defaults || value != defaultValue) {
   235        this.addFloat32(value);
   236        this.slot(voffset);
   237      }
   238    }
   239  
   240    addFieldFloat64(voffset: number, value: number, defaultValue: number): void {
   241      if (this.force_defaults || value != defaultValue) {
   242        this.addFloat64(value);
   243        this.slot(voffset);
   244      }
   245    }
   246  
   247    addFieldOffset(voffset: number, value: Offset, defaultValue: Offset): void {
   248      if (this.force_defaults || value != defaultValue) {
   249        this.addOffset(value);
   250        this.slot(voffset);
   251      }
   252    }
   253  
   254    /**
   255     * Structs are stored inline, so nothing additional is being added. `d` is always 0.
   256     */
   257    addFieldStruct(voffset: number, value: Offset, defaultValue: Offset): void {
   258      if (value != defaultValue) {
   259        this.nested(value);
   260        this.slot(voffset);
   261      }
   262    }
   263  
   264    /**
   265     * Structures are always stored inline, they need to be created right
   266     * where they're used.  You'll get this assertion failure if you
   267     * created it elsewhere.
   268     */
   269    nested(obj: Offset): void {
   270      if (obj != this.offset()) {
   271        throw new TypeError('FlatBuffers: struct must be serialized inline.');
   272      }
   273    }
   274  
   275    /**
   276     * Should not be creating any other object, string or vector
   277     * while an object is being constructed
   278     */
   279    notNested(): void {
   280      if (this.isNested) {
   281        throw new TypeError('FlatBuffers: object serialization must not be nested.');
   282      }
   283    }
   284  
   285    /**
   286     * Set the current vtable at `voffset` to the current location in the buffer.
   287     */
   288    slot(voffset: number): void {
   289      if (this.vtable !== null)
   290        this.vtable[voffset] = this.offset();
   291    }
   292  
   293    /**
   294     * @returns Offset relative to the end of the buffer.
   295     */
   296    offset(): Offset {
   297      return this.bb.capacity() - this.space;
   298    }
   299  
   300    /**
   301     * Doubles the size of the backing ByteBuffer and copies the old data towards
   302     * the end of the new buffer (since we build the buffer backwards).
   303     *
   304     * @param bb The current buffer with the existing data
   305     * @returns A new byte buffer with the old data copied
   306     * to it. The data is located at the end of the buffer.
   307     *
   308     * uint8Array.set() formally takes {Array<number>|ArrayBufferView}, so to pass
   309     * it a uint8Array we need to suppress the type check:
   310     * @suppress {checkTypes}
   311     */
   312    static growByteBuffer(bb: ByteBuffer): ByteBuffer {
   313      const old_buf_size = bb.capacity();
   314  
   315      // Ensure we don't grow beyond what fits in an int.
   316      if (old_buf_size & 0xC0000000) {
   317        throw new Error('FlatBuffers: cannot grow buffer beyond 2 gigabytes.');
   318      }
   319  
   320      const new_buf_size = old_buf_size << 1;
   321      const nbb = ByteBuffer.allocate(new_buf_size);
   322      nbb.setPosition(new_buf_size - old_buf_size);
   323      nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size);
   324      return nbb;
   325    }
   326  
   327    /**
   328     * Adds on offset, relative to where it will be written.
   329     *
   330     * @param offset The offset to add.
   331     */
   332    addOffset(offset: Offset): void {
   333      this.prep(SIZEOF_INT, 0); // Ensure alignment is already done.
   334      this.writeInt32(this.offset() - offset + SIZEOF_INT);
   335    }
   336  
   337    /**
   338     * Start encoding a new object in the buffer.  Users will not usually need to
   339     * call this directly. The FlatBuffers compiler will generate helper methods
   340     * that call this method internally.
   341     */
   342    startObject(numfields: number): void {
   343      this.notNested();
   344      if (this.vtable == null) {
   345        this.vtable = [];
   346      }
   347      this.vtable_in_use = numfields;
   348      for (let i = 0; i < numfields; i++) {
   349        this.vtable[i] = 0; // This will push additional elements as needed
   350      }
   351      this.isNested = true;
   352      this.object_start = this.offset();
   353    }
   354  
   355    /**
   356     * Finish off writing the object that is under construction.
   357     *
   358     * @returns The offset to the object inside `dataBuffer`
   359     */
   360    endObject(): Offset {
   361      if (this.vtable == null || !this.isNested) {
   362        throw new Error('FlatBuffers: endObject called without startObject');
   363      }
   364  
   365      this.addInt32(0);
   366      const vtableloc = this.offset();
   367  
   368      // Trim trailing zeroes.
   369      let i = this.vtable_in_use - 1;
   370      // eslint-disable-next-line no-empty
   371      for (; i >= 0 && this.vtable[i] == 0; i--) {}
   372      const trimmed_size = i + 1;
   373  
   374      // Write out the current vtable.
   375      for (; i >= 0; i--) {
   376        // Offset relative to the start of the table.
   377        this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0);
   378      }
   379  
   380      const standard_fields = 2; // The fields below:
   381      this.addInt16(vtableloc - this.object_start);
   382      const len = (trimmed_size + standard_fields) * SIZEOF_SHORT;
   383      this.addInt16(len);
   384  
   385      // Search for an existing vtable that matches the current one.
   386      let existing_vtable = 0;
   387      const vt1 = this.space;
   388    outer_loop:
   389      for (i = 0; i < this.vtables.length; i++) {
   390        const vt2 = this.bb.capacity() - this.vtables[i];
   391        if (len == this.bb.readInt16(vt2)) {
   392          for (let j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) {
   393            if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) {
   394              continue outer_loop;
   395            }
   396          }
   397          existing_vtable = this.vtables[i];
   398          break;
   399        }
   400      }
   401  
   402      if (existing_vtable) {
   403        // Found a match:
   404        // Remove the current vtable.
   405        this.space = this.bb.capacity() - vtableloc;
   406  
   407        // Point table to existing vtable.
   408        this.bb.writeInt32(this.space, existing_vtable - vtableloc);
   409      } else {
   410        // No match:
   411        // Add the location of the current vtable to the list of vtables.
   412        this.vtables.push(this.offset());
   413  
   414        // Point table to current vtable.
   415        this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc);
   416      }
   417  
   418      this.isNested = false;
   419      return vtableloc as Offset;
   420    }
   421  
   422    /**
   423     * Finalize a buffer, poiting to the given `root_table`.
   424     */
   425    finish(root_table: Offset, opt_file_identifier?: string, opt_size_prefix?: boolean): void {
   426      const size_prefix = opt_size_prefix ? SIZE_PREFIX_LENGTH : 0;
   427      if (opt_file_identifier) {
   428        const file_identifier = opt_file_identifier;
   429        this.prep(this.minalign, SIZEOF_INT +
   430          FILE_IDENTIFIER_LENGTH + size_prefix);
   431        if (file_identifier.length != FILE_IDENTIFIER_LENGTH) {
   432          throw new TypeError('FlatBuffers: file identifier must be length ' +
   433            FILE_IDENTIFIER_LENGTH);
   434        }
   435        for (let i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) {
   436          this.writeInt8(file_identifier.charCodeAt(i));
   437        }
   438      }
   439      this.prep(this.minalign, SIZEOF_INT + size_prefix);
   440      this.addOffset(root_table);
   441      if (size_prefix) {
   442        this.addInt32(this.bb.capacity() - this.space);
   443      }
   444      this.bb.setPosition(this.space);
   445    }
   446  
   447    /**
   448     * Finalize a size prefixed buffer, pointing to the given `root_table`.
   449     */
   450    finishSizePrefixed(this: Builder, root_table: Offset, opt_file_identifier?: string): void {
   451      this.finish(root_table, opt_file_identifier, true);
   452    }
   453  
   454    /**
   455     * This checks a required field has been set in a given table that has
   456     * just been constructed.
   457     */
   458    requiredField(table: Offset, field: number): void {
   459      const table_start = this.bb.capacity() - table;
   460      const vtable_start = table_start - this.bb.readInt32(table_start);
   461      const ok = field < this.bb.readInt16(vtable_start) &&
   462                 this.bb.readInt16(vtable_start + field) != 0;
   463  
   464      // If this fails, the caller will show what field needs to be set.
   465      if (!ok) {
   466        throw new TypeError('FlatBuffers: field ' + field + ' must be set');
   467      }
   468    }
   469  
   470    /**
   471     * Start a new array/vector of objects.  Users usually will not call
   472     * this directly. The FlatBuffers compiler will create a start/end
   473     * method for vector types in generated code.
   474     *
   475     * @param elem_size The size of each element in the array
   476     * @param num_elems The number of elements in the array
   477     * @param alignment The alignment of the array
   478     */
   479    startVector(elem_size: number, num_elems: number, alignment: number): void {
   480      this.notNested();
   481      this.vector_num_elems = num_elems;
   482      this.prep(SIZEOF_INT, elem_size * num_elems);
   483      this.prep(alignment, elem_size * num_elems); // Just in case alignment > int.
   484    }
   485  
   486    /**
   487     * Finish off the creation of an array and all its elements. The array must be
   488     * created with `startVector`.
   489     *
   490     * @returns The offset at which the newly created array
   491     * starts.
   492     */
   493    endVector(): Offset {
   494      this.writeInt32(this.vector_num_elems);
   495      return this.offset();
   496    }
   497  
   498    /**
   499     * Encode the string `s` in the buffer using UTF-8. If the string passed has 
   500     * already been seen, we return the offset of the already written string
   501     *
   502     * @param s The string to encode
   503     * @return The offset in the buffer where the encoded string starts
   504     */
   505    createSharedString(s: string | Uint8Array): Offset {
   506      if (!s) { return 0 }
   507  
   508      if (!this.string_maps) {
   509        this.string_maps = new Map();
   510      }
   511  
   512      if (this.string_maps.has(s)) {
   513        return this.string_maps.get(s) as Offset
   514      }
   515      const offset = this.createString(s)
   516      this.string_maps.set(s, offset)
   517      return offset
   518    }
   519  
   520    /**
   521     * Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed
   522     * instead of a string, it is assumed to contain valid UTF-8 encoded data.
   523     *
   524     * @param s The string to encode
   525     * @return The offset in the buffer where the encoded string starts
   526     */
   527    createString(s: string | Uint8Array | null | undefined): Offset {
   528      if (s === null || s === undefined) {
   529        return 0;
   530      }
   531
   532      let utf8: string | Uint8Array | number[];
   533      if (s instanceof Uint8Array) {
   534        utf8 = s;
   535      } else {
   536        utf8 = this.text_encoder.encode(s);
   537      }
   538  
   539      this.addInt8(0);
   540      this.startVector(1, utf8.length, 1);
   541      this.bb.setPosition(this.space -= utf8.length);
   542      for (let i = 0, offset = this.space, bytes = this.bb.bytes(); i < utf8.length; i++) {
   543        bytes[offset++] = utf8[i];
   544      }
   545      return this.endVector();
   546    }
   547  
   548    /**
   549     * A helper function to pack an object
   550     * 
   551     * @returns offset of obj
   552     */
   553    createObjectOffset(obj: string | IGeneratedObject | null): Offset {
   554      if(obj === null) {
   555        return 0
   556      }
   557  
   558      if(typeof obj === 'string') {
   559        return this.createString(obj);
   560      } else {
   561        return obj.pack(this);
   562      }
   563    }
   564  
   565    /**
   566     * A helper function to pack a list of object
   567     * 
   568     * @returns list of offsets of each non null object
   569     */
   570    createObjectOffsetList(list: (string | IGeneratedObject)[]): Offset[] {
   571      const ret: number[] = [];
   572  
   573      for(let i = 0; i < list.length; ++i) {
   574        const val = list[i];
   575  
   576        if(val !== null) {
   577          ret.push(this.createObjectOffset(val));
   578        } else {
   579          throw new TypeError(
   580            'FlatBuffers: Argument for createObjectOffsetList cannot contain null.'); 
   581        }
   582      }
   583      
   584      return ret;
   585    }
   586  
   587    createStructOffsetList(list: (string | IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void): Offset {
   588      startFunc(this, list.length);
   589      this.createObjectOffsetList(list.slice().reverse());
   590      return this.endVector();
   591    }
   592  }

View as plain text