UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

1,388 lines (1,116 loc) • 36.9 kB
/** * * @enum {boolean} */ import { assert } from "../assert.js"; import { array_copy } from "../collection/array/array_copy.js"; import { array_buffer_copy } from "../collection/array/typed/array_buffer_copy.js"; import { align_4 } from "./align_4.js"; import { EndianType } from "./EndianType.js"; import { half_to_float_uint16 } from "./half_to_float_uint16.js"; import { to_half_float_uint16 } from "./to_half_float_uint16.js"; /** * Minimum number of bytes to grow the buffer by when the buffer is full. * @type {number} */ const MIN_GROWTH_STEP = 1024; /** * 2^31-1, values above this will be cropped incorrectly when bit-shifting * @type {number} */ const MAX_SAFE_UINT_VAR = 2147483647; /** * @readonly * @type {number} */ const DEFAULT_INITIAL_SIZE = 1024; /** * Utility for reading/writing binary data. * Mostly useful for serialization/deserialization tasks. * The buffer is dynamically resized, so you do not need to manage the size manually. * It is useful to think of this structure as a "stream". * * @example * const buffer = new BinaryBuffer(); * * buffer.writeUTF8String("Hello World"); * * buffer.position = 0; // rewind to the beginning * * const deserialized = buffer.readUTF8String(); // "Hello World" * * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class BinaryBuffer { /** * Default is little-endian as most platforms operate in little-endian * The reason this is fixed is to ensure cross-platform compatibility as endianness in JavaScript is platform-dependent. * @see https://en.wikipedia.org/wiki/Endianness * @type {EndianType|boolean} */ endianness = EndianType.LittleEndian; /** * Current position in the buffer, where read and write operations will occur. * Make sure to set this to the correct value before reading/writing data. * Typically, this is set to 0 before reading/writing data. * @type {number} */ position = 0; /** * @deprecated */ get length() { throw new Error("Deprecated, use 'capacity' instead"); } /** * @deprecated */ set length(v) { throw new Error("Deprecated, use 'capacity' instead"); } /** * Managed by the buffer, do not modify directly * @type {number} */ capacity = DEFAULT_INITIAL_SIZE; /** * Raw underlying bytes attached to the buffer, note that this is managed by the `BinaryBuffer` and can grow/shrink as needed. * @see setCapacity * @see trim * @type {ArrayBuffer} * @private */ data = new ArrayBuffer(DEFAULT_INITIAL_SIZE); /** * Bound to the {@link data} buffer, do not modify directly. * @type {DataView} * @private */ dataView = new DataView(this.data); /** * Bound to the {@link data} buffer, do not modify directly. * @type {Uint8Array} * @private */ __data_uint8 = new Uint8Array(this.data); /** * When the buffer grows in size, this is the multiplication factor by which it grows. * @type {number} * @private */ __growFactor = 1.1; /** * Access raw underlying bytes attached to the buffer * @return {Uint8Array} */ get raw_bytes() { return this.__data_uint8; } /** * Sets {@link capacity} to the size of the input data. * Sets {@link position} to 0. * * Note: if you write to the buffer past the size of the input data {@link ArrayBuffer} - bound data will be re-allocated. * @param {ArrayBuffer} data */ fromArrayBuffer(data) { assert.defined(data, 'data'); assert.notNull(data, 'data'); this.data = data; this.dataView = new DataView(data); this.__data_uint8 = new Uint8Array(data); this.capacity = data.byteLength; this.position = 0; } /** * Set capacity to contain data only up to the current `position`. * This will re-allocate the `data` buffer if necessary. * @returns {BinaryBuffer} */ trim() { this.setCapacity(this.position); return this; } /** * Advance `position`(read/write cursor) a certain number of bytes forward. * * @param {number} byte_count */ skip(byte_count) { assert.isNonNegativeInteger(byte_count, 'byte_count'); this.position += byte_count; } /** * This will re-allocate {@link data} buffer if necessary. * Note that all data is retained. * Cannot shink past the current `position`. * * @param {number} capacity * @throws {Error} if requested capacity is less than current `position` * */ setCapacity(capacity) { assert.isNonNegativeInteger(capacity, 'capacity'); if (capacity < this.position) { throw new Error(`Attempting to set capacity(=${capacity}) below current position(=${this.position})`); } if (this.capacity === capacity) { // already the right capacity return; } const oldData = this.__data_uint8; const newData = new Uint8Array(capacity); if(this.position > 0) { //copy old data array_buffer_copy( oldData.buffer, 0, newData.buffer, 0, Math.min( oldData.buffer.byteLength, newData.buffer.byteLength, this.position ) ); } this.data = newData.buffer; this.__data_uint8 = newData; this.dataView = new DataView(this.data); this.capacity = capacity; } /** * * @param {number} min_capacity */ ensureCapacity(min_capacity) { assert.isNumber(min_capacity, 'min_capacity'); assert.greaterThanOrEqual(min_capacity, 0, 'min_capacity'); assert.notNaN(min_capacity, 'min_capacity'); const existing_capacity = this.capacity; if (existing_capacity >= min_capacity) { return; } const rough_new_capacity = Math.ceil(Math.max( min_capacity, existing_capacity * this.__growFactor, existing_capacity + MIN_GROWTH_STEP )); // align for easier memory operations const aligned_new_capacity = align_4(rough_new_capacity); this.setCapacity(aligned_new_capacity); } /** * * @returns {number} */ readFloat16() { const u16 = this.readUint16(); return half_to_float_uint16(u16); } /** * * @returns {number} */ readFloat32() { const result = this.dataView.getFloat32(this.position, this.endianness); this.position += 4; return result; } /** * * @returns {number} */ readFloat64() { const result = this.dataView.getFloat64(this.position, this.endianness); this.position += 8; return result; } /** * * @return {number} */ readInt8() { const result = this.dataView.getInt8(this.position); this.position += 1; return result; } /** * * @return {number} */ readInt16() { const result = this.dataView.getInt16(this.position, this.endianness); this.position += 2; return result; } /** * * @returns {number} */ readInt32() { const result = this.dataView.getInt32(this.position, this.endianness); this.position += 4; return result; } /** * * @returns {number} */ readUint8() { const result = this.dataView.getUint8(this.position); this.position += 1; return result; } /** * * @returns {number} */ readUint16() { const result = this.dataView.getUint16(this.position, this.endianness); this.position += 2; return result; } /** * * @returns {number} */ readUint16LE() { const result = this.dataView.getUint16(this.position, EndianType.LittleEndian); this.position += 2; return result; } /** * * @returns {number} */ readUint16BE() { const result = this.dataView.getUint16(this.position, EndianType.BigEndian); this.position += 2; return result; } /** * * @returns {number} */ readUint24() { if (this.endianness === EndianType.BigEndian) { return this.readUint24BE(); } else { return this.readUint24LE(); } } /** * * @returns {number} */ readUint24LE() { const b0 = this.dataView.getUint8(this.position); const b1 = this.dataView.getUint8(this.position + 1); const b2 = this.dataView.getUint8(this.position + 2); this.position += 3; return b0 | (b1 << 8) | (b2 << 16); } /** * * @returns {number} */ readUint24BE() { const b0 = this.dataView.getUint8(this.position); const b1 = this.dataView.getUint8(this.position + 1); const b2 = this.dataView.getUint8(this.position + 2); this.position += 3; return b2 | (b1 << 8) | (b0 << 16); } /** * * @returns {number} */ readUint32() { const result = this.dataView.getUint32(this.position, this.endianness); this.position += 4; return result; } /** * * @returns {number} */ readUint32LE() { const result = this.dataView.getUint32(this.position, EndianType.LittleEndian); this.position += 4; return result; } /** * * @returns {number} */ readUint32BE() { const result = this.dataView.getUint32(this.position, EndianType.BigEndian); this.position += 4; return result; } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Uint8Array} destination */ readUint8Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readUint8(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Uint16Array} destination */ readUint16Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readUint16(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Uint32Array|number[]|ArrayLike<number>} destination */ readUint32Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readUint32(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Int8Array} destination */ readInt8Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readInt8(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Int16Array} destination */ readInt16Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readInt16(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Int32Array} destination */ readInt32Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readInt32(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Float32Array|number[]} destination */ readFloat32Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readFloat32(); } } /** * * @param {number} destination_offset starting index in the destination array * @param {number} length number of elements to read * @param {Float64Array} destination */ readFloat64Array(destination, destination_offset, length) { for (let i = 0; i < length; i++) { destination[i + destination_offset] = this.readFloat64(); } } /** * * @param {number} source_offset starting index in the source array * @param {number} length number of elements to read * @param {Float32Array|number[]} source */ writeFloat32Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); for (let i = 0; i < length; i++) { this.writeFloat32(source[i + source_offset]); } } /** * * @param {number} value */ writeFloat16(value) { const u16 = to_half_float_uint16(value); this.writeUint16(u16); } /** * * @param {number} value */ writeFloat32(value) { const end = this.position + 4; this.ensureCapacity(end); this.dataView.setFloat32(this.position, value, this.endianness); this.position = end; } /** * * @param {number} value */ writeFloat64(value) { const end = this.position + 8; this.ensureCapacity(end); this.dataView.setFloat64(this.position, value, this.endianness); this.position = end; } /** * * @param {number} value */ writeInt8(value) { const end = this.position + 1; this.ensureCapacity(end); this.dataView.setInt8(this.position, value); this.position = end; } /** * * @param {number} value */ writeInt16(value) { const end = this.position + 2; this.ensureCapacity(end); this.dataView.setInt16(this.position, value, this.endianness); this.position = end; } /** * * @param {number} value */ writeInt32(value) { const end = this.position + 4; this.ensureCapacity(end); this.dataView.setInt32(this.position, value, this.endianness); this.position = end; } /** * * @param {Int8Array|number[]|ArrayLike<number>} source * @param {number} source_offset * @param {number} length */ writeInt8Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); // prevent resizing mid-copy this.ensureCapacity(this.position + length); for (let i = 0; i < length; i++) { this.writeInt8(source[source_offset + i]); } } /** * * @param {Int16Array|number[]|ArrayLike<number>} source * @param {number} source_offset * @param {number} length */ writeInt16Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); // prevent resizing mid-copy this.ensureCapacity(this.position + 2 * length); for (let i = 0; i < length; i++) { this.writeInt16(source[source_offset + i]); } } /** * * @param {Int32Array|number[]|ArrayLike<number>} source * @param {number} source_offset * @param {number} length */ writeInt32Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); // prevent resizing mid-copy this.ensureCapacity(this.position + 4 * length); for (let i = 0; i < length; i++) { this.writeInt32(source[source_offset + i]); } } /** * * @param {number} value */ writeUint8(value) { const end = this.position + 1; this.ensureCapacity(end); this.dataView.setUint8(this.position, value); this.position = end; } /** * * @param {Uint8Array|number[]} source * @param {number} source_offset * @param {number} length */ writeUint8Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); for (let i = 0; i < length; i++) { this.writeUint8(source[source_offset + i]); } } /** * * @param {number} value */ writeUint16(value) { const end = this.position + 2; this.ensureCapacity(end); this.dataView.setUint16(this.position, value, this.endianness); this.position = end; } /** * * @param {number} value */ writeUint16BE(value) { const end = this.position + 2; this.ensureCapacity(end); this.dataView.setUint16(this.position, value, EndianType.BigEndian); this.position = end; } /** * * @param {number} value */ writeUint16LE(value) { const end = this.position + 2; this.ensureCapacity(end); this.dataView.setUint16(this.position, value, EndianType.LittleEndian); this.position = end; } /** * * @param {Uint16Array|number[]} source * @param {number} source_offset * @param {number} length */ writeUint16Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); for (let i = 0; i < length; i++) { this.writeUint16(source[source_offset + i]); } } /** * * @param {number} value */ writeUint24(value) { if (this.endianness === EndianType.BigEndian) { this.writeUint24BE(value); } else { this.writeUint24LE(value); } } /** * * @param {number} value */ writeUint24BE(value) { const end = this.position + 3; this.ensureCapacity(end); const b0 = value & 0xFF; const b1 = (value >> 8) & 0xFF; const b2 = (value >> 16) & 0xFF; this.dataView.setUint8(this.position, b2); this.dataView.setUint8(this.position + 1, b1); this.dataView.setUint8(this.position + 2, b0); this.position = end; } /** * * @param {number} value */ writeUint24LE(value) { const end = this.position + 3; this.ensureCapacity(end); const b0 = value & 0xFF; const b1 = (value >> 8) & 0xFF; const b2 = (value >> 16) & 0xFF; this.dataView.setUint8(this.position, b0); this.dataView.setUint8(this.position + 1, b1); this.dataView.setUint8(this.position + 2, b2); this.position = end; } /** * Write uint using a minimum number of bytes. * Compact encoding scheme, if the value is 127 or less - only one byte will be used, if the value is 16383 or less - two bytes will be used, etc. * NOTE: uses 7-bit encoding with 1 bit used for carry-over flag. * NOTE: explicitly a little-endian format, {@link endianness} is ignored. * @param {number} value must be an unsigned integer */ writeUintVar(value) { assert.isNonNegativeInteger(value, 'value'); assert.ok(value <= MAX_SAFE_UINT_VAR, `value=[${value}] exceeds maximum safe limit[=${MAX_SAFE_UINT_VAR}]`); let first = true; let _v = value; while (first || _v !== 0) { first = false; let lower7bits = (_v & 0x7f); _v >>= 7; if (_v > 0) { //write carry-over flag lower7bits |= 128; } this.writeUint8(lower7bits); } } /** * Read Uint of variable length, a compliment to {@link #writeUintVar} * @returns {number} */ readUintVar() { let more = true; let value = 0; let shift = 0; while (more) { let lower7bits = this.readUint8(); //read carry-over flag more = (lower7bits & 128) !== 0; //read value part of the byte value |= (lower7bits & 0x7f) << shift; //increment shift shift += 7; } return value; } /** * * @param {number} value */ writeUint32(value) { const end = this.position + 4; this.ensureCapacity(end); this.dataView.setUint32(this.position, value, this.endianness); this.position = end; } /** * * @param {number} value */ writeUint32BE(value) { const end = this.position + 4; this.ensureCapacity(end); this.dataView.setUint32(this.position, value, EndianType.BigEndian); this.position = end; } /** * * @param {number} value */ writeUint32LE(value) { const end = this.position + 4; this.ensureCapacity(end); this.dataView.setUint32(this.position, value, EndianType.LittleEndian); this.position = end; } /** * * @param {Uint32Array|number[]|ArrayLike<number>} source * @param {number} source_offset * @param {number} length */ writeUint32Array(source, source_offset, length) { assert.isArrayLike(source, 'source'); assert.greaterThanOrEqual(source.length, source_offset + length, "source underflow"); // prevent resizing mid-copy this.ensureCapacity(this.position + 4 * length); for (let i = 0; i < length; i++) { this.writeUint32(source[source_offset + i]); } } /** * * @param {Uint8Array|Uint8ClampedArray} array * @param {number} source_offset * @param {number} length */ writeBytes(array, source_offset, length) { const source_end = source_offset + length; assert.greaterThanOrEqual(array.length, source_end, 'source array underflow'); const targetAddress = this.position; const end = targetAddress + length; this.ensureCapacity(end); if (source_offset === 0 && array.length === length) { // copying entire source array this.__data_uint8.set(array, targetAddress); } else if (typeof array.subarray === "function") { // typed array, use "subarray" method this.__data_uint8.set(array.subarray(source_offset, source_end), targetAddress); } else { // not a typed array, copy byte by byte manually for (let i = 0; i < length; i++) { this.__data_uint8[targetAddress + i] = array[source_offset + i]; } } this.position = end; } /** * * @param {Uint8Array} destination * @param {number} destination_offset * @param {number} length */ readBytes(destination, destination_offset, length) { const source_position = this.position; const end = source_position + length; const uint8 = this.__data_uint8; if (length < 128) { // small copy array_copy(uint8, source_position, destination, destination_offset, length); } else { destination.set(uint8.subarray(source_position, end), destination_offset); } this.position = end; } /** * * @param {string} string */ writeUTF8String(string) { // Adapted from https://github.com/samthor/fast-text-encoding/blob/master/text.js // Original license is Apache 2.0 if (string === null) { //mark NULL this.writeUint32(4294967295); //bail, no string data to write return; } else if (string === undefined) { //mark undefined this.writeUint32(4294967294); return; } let pos = 0; const len = string.length; if (len >= 4294967294) { throw new Error('String is too long'); } //mark non-NULL this.writeUint32(len); let cursor = this.position; // output position const expected_byte_size = Math.max(32, len + (len >> 1) + 7); // 1.5x size this.ensureCapacity(expected_byte_size + cursor); let target = this.__data_uint8; let capacity = this.capacity; while (pos < len) { let value = string.charCodeAt(pos++); if (value >= 0xd800 && value <= 0xdbff) { // high surrogate if (pos < len) { const extra = string.charCodeAt(pos); if ((extra & 0xfc00) === 0xdc00) { ++pos; value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; } } if (value >= 0xd800 && value <= 0xdbff) { continue; // drop lone surrogate } } // expand the buffer if we couldn't write 4 bytes if (cursor + 4 > capacity) { this.ensureCapacity(cursor + 4); // rebind variables capacity = this.capacity; target = this.__data_uint8; } if ((value & 0xffffff80) === 0) { // 1-byte target[cursor++] = value; // ASCII continue; } else if ((value & 0xfffff800) === 0) { // 2-byte target[cursor++] = ((value >> 6) & 0x1f) | 0xc0; } else if ((value & 0xffff0000) === 0) { // 3-byte target[cursor++] = ((value >> 12) & 0x0f) | 0xe0; target[cursor++] = ((value >> 6) & 0x3f) | 0x80; } else if ((value & 0xffe00000) === 0) { // 4-byte target[cursor++] = ((value >> 18) & 0x07) | 0xf0; target[cursor++] = ((value >> 12) & 0x3f) | 0x80; target[cursor++] = ((value >> 6) & 0x3f) | 0x80; } else { // FIXME: do we care? continue; } target[cursor++] = (value & 0x3f) | 0x80; } this.position = cursor; } /** * * @returns {string} */ readUTF8String() { // Adapted from https://github.com/samthor/fast-text-encoding/blob/master/text.js // Original license is Apache 2.0 //check for null const stringLength = this.readUint32(); if (stringLength === 4294967295) { //null string return null; } else if (stringLength === 4294967294) { //undefined string return undefined; } const bytes = this.__data_uint8; let result = ""; let i = this.position; let charCount = 0; while (i < this.capacity && charCount < stringLength) { const byte1 = bytes[i++]; let codePoint; if (byte1 === 0) { break; // NULL } if ((byte1 & 0x80) === 0) { // 1-byte codePoint = byte1; } else if ((byte1 & 0xe0) === 0xc0) { // 2-byte const byte2 = bytes[i++] & 0x3f; codePoint = (((byte1 & 0x1f) << 6) | byte2); } else if ((byte1 & 0xf0) === 0xe0) { const byte2 = bytes[i++] & 0x3f; const byte3 = bytes[i++] & 0x3f; codePoint = (((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3); } else if ((byte1 & 0xf8) === 0xf0) { const byte2 = bytes[i++] & 0x3f; const byte3 = bytes[i++] & 0x3f; const byte4 = bytes[i++] & 0x3f; // this can be > 0xffff, so possibly generate surrogates codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; if (codePoint > 0xffff) { // codepoint &= ~0x10000; codePoint -= 0x10000; result += String.fromCharCode((codePoint >>> 10) & 0x3ff | 0xd800); charCount++; codePoint = 0xdc00 | codePoint & 0x3ff; } } else { // FIXME: we're ignoring this } charCount++; result += String.fromCharCode(codePoint); } this.position = i; return result; } /** * Write an ASCII (American Standard Code for Information Interchange) string. If the string contains characters that are not representable by ASCII, an error will be thrown. * Note that ASCII only has 128 code points (characters), so this method is not suitable for representing UTF-8 strings. * If the string is not ASCII representable - use {@link writeUTF8String} instead. * * @see https://en.wikipedia.org/wiki/ASCII * * @param {string} string */ writeASCIIString(string) { assert.isString(string, 'string'); const char_count = string.length; const start = this.position; const end = start + char_count; this.ensureCapacity(end); for (let i = 0; i < char_count; i++) { const char_code = string.charCodeAt(i); if (char_code > 0x80) { throw new Error(`Character ${String.fromCharCode(char_code)} can\'t be represented by a US-ASCII byte.`); } this.__data_uint8[start + i] = char_code; } this.position = end; } /** * Read ASCII (American Standard Code for Information Interchange) characters to the buffer. * Input is not validated, if the string contains non-ASCII characters, the result is unspecified. * * @see https://en.wikipedia.org/wiki/ASCII * * @param {number} length maximum number of characters to read. If `null_terminated` flag is on, resulting string might be shorter than `length` * @param {boolean} [null_terminated] if true will stop reading when encountering 0 byte value character (NULL) * @returns {string} */ readASCIICharacters(length, null_terminated = false) { assert.isNonNegativeInteger(length, 'length'); assert.isBoolean(null_terminated, 'null_terminated'); let result = ""; for (let i = 0; i < length; i++) { const code = this.readUint8(); if (null_terminated && code === 0) { // null-termination break; } result += String.fromCharCode(code); } return result; } /** * Represent the object as a string. Useful mainly for debugging. * * @return {string} */ toString() { return `BinaryBuffer[position=${this.position}, capacity=${this.capacity}, endianness=${this.endianness}]`; } /** * Useful for debugging, outputs contents of the buffer in hex format. * Only includes data up to the .position * * @example * const b = new BinaryBuffer(); * b.writeUint8(0xCA); * b.writeUint8(0xFE); * b.toHexString(); // "CAFE" * * @return {string} */ toHexString() { const uint8 = this.__data_uint8; const end = Math.min(uint8.length, this.position); let result = ''; for (let i = 0; i < end; i++) { const byte = uint8[i]; result += byte.toString(16).padStart(2, '0').toUpperCase(); } return result; } /** * * @param {EndianType} type * @return {BinaryBuffer} */ static fromEndianness(type) { assert.enum(type, EndianType, 'type'); const r = new BinaryBuffer(); r.endianness = type; return r; } /** * * @param {ArrayBuffer} v * @return {BinaryBuffer} */ static fromArrayBuffer(v) { const r = new BinaryBuffer(); r.fromArrayBuffer(v); return r; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {string} Copied value */ static copyUTF8String(source, target) { const v = source.readUTF8String(); target.writeUTF8String(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyUintVar(source, target) { const v = source.readUintVar(); target.writeUintVar(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyUint8(source, target) { const v = source.readUint8(); target.writeUint8(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyUint16(source, target) { const v = source.readUint16(); target.writeUint16(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyUint32(source, target) { const v = source.readUint32(); target.writeUint32(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyFloat32(source, target) { const v = source.readFloat32(); target.writeFloat32(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @returns {number} Copied value */ static copyFloat64(source, target) { const v = source.readFloat64(); target.writeFloat64(v); return v; } /** * * @param {BinaryBuffer} source * @param {BinaryBuffer} target * @param {number} length number of bytes to copy * @returns {Uint8Array} Copied data */ static copyBytes(source, target, length) { const temp = new Uint8Array(length); source.readBytes(temp, 0, length); target.writeBytes(temp, 0, length); return temp; } } /** * @readonly * @type {boolean} */ BinaryBuffer.prototype.isBinaryBuffer = true;