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