flatbuffers

Форк
0
/
builder.ts 
607 строк · 18.8 Кб
1
import { ByteBuffer } from "./byte-buffer.js"
2
import { SIZEOF_SHORT, SIZE_PREFIX_LENGTH, SIZEOF_INT, FILE_IDENTIFIER_LENGTH } from "./constants.js"
3
import { Offset, IGeneratedObject } from "./types.js"
4

5
export 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|null): 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|null): 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|null): 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|null): 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|null): 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|null): 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
      this.bb.bytes().set(utf8, this.space);
543
      return this.endVector();
544
    }
545
  
546
    /**
547
     * Create a byte vector.
548
     *
549
     * @param v The bytes to add
550
     * @returns The offset in the buffer where the byte vector starts
551
     */
552
    createByteVector(v: Uint8Array | null | undefined): Offset {
553
      if (v === null || v === undefined) {
554
        return 0;
555
      }
556

557
      this.startVector(1, v.length, 1);
558
      this.bb.setPosition(this.space -= v.length);
559
      this.bb.bytes().set(v, this.space);
560
      return this.endVector();
561
    }
562
  
563
    /**
564
     * A helper function to pack an object
565
     * 
566
     * @returns offset of obj
567
     */
568
    createObjectOffset(obj: string | IGeneratedObject | null): Offset {
569
      if(obj === null) {
570
        return 0
571
      }
572
  
573
      if(typeof obj === 'string') {
574
        return this.createString(obj);
575
      } else {
576
        return obj.pack(this);
577
      }
578
    }
579
  
580
    /**
581
     * A helper function to pack a list of object
582
     * 
583
     * @returns list of offsets of each non null object
584
     */
585
    createObjectOffsetList(list: (string | IGeneratedObject)[]): Offset[] {
586
      const ret: number[] = [];
587
  
588
      for(let i = 0; i < list.length; ++i) {
589
        const val = list[i];
590
  
591
        if(val !== null) {
592
          ret.push(this.createObjectOffset(val));
593
        } else {
594
          throw new TypeError(
595
            'FlatBuffers: Argument for createObjectOffsetList cannot contain null.'); 
596
        }
597
      }
598
      
599
      return ret;
600
    }
601
  
602
    createStructOffsetList(list: (string | IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void): Offset {
603
      startFunc(this, list.length);
604
      this.createObjectOffsetList(list.slice().reverse());
605
      return this.endVector();
606
    }
607
  }
608

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.