UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

264 lines (192 loc) • 6.34 kB
import { assert } from "../../../../../core/assert.js"; import { HashMap } from "../../../../../core/collection/map/HashMap.js"; import { returnEmptyArray } from "../../../../../core/function/returnEmptyArray.js"; import { BinaryCollectionHeaderCodec, BinaryCollectionHeaderLayout } from "./BinaryCollectionHeaderCodec.js"; export class BinaryCollectionSerializer { /** * @private * @type {BinarySerializationRegistry} */ registry = null; /** * @private * @type {BinaryBuffer} */ buffer = null; /** * @private * @type {BinaryClassSerializationAdapter} */ adapter = null; /** * * @type {String} */ className = null; /** * * @type {boolean} * @private */ __dictionaryEnabled = false; /** * @private * @type {number} */ elementCount = 0; /** * @private * @type {number} */ startAddress = 0; /** * @private * @type {number} */ headerAddress = 0; /** * @private * @type {Map<any, number>} */ dictionary = new HashMap({ capacity: 1024, //pre-allocate larger size to avoid rehashing }); /** * * @param {string} className */ setClass(className) { assert.isString(className, 'className'); this.className = className; } /** * * @param {BinarySerializationRegistry} registry */ setRegistry(registry) { assert.notEqual(registry, undefined, 'registry is undefined'); assert.notEqual(registry, null, 'registry is null'); this.registry = registry; } /** * * @param {BinaryBuffer} buffer */ setBuffer(buffer) { assert.notEqual(buffer, undefined, 'buffer is undefined'); assert.notEqual(buffer, null, 'buffer is null'); this.buffer = buffer; } /** * * @returns {number} */ getElementCount() { return this.elementCount; } /** * * @param {function(string,Class, BinaryClassSerializationAdapter):[]} [adapterOptionsSupplier] * @param {function(string):*} [problemConsumer] * @returns {boolean} */ initialize({ adapterOptionsSupplier = returnEmptyArray, problemConsumer = console.warn } = {}) { assert.isFunction(adapterOptionsSupplier, 'adapterOptionsSupplier'); const className = this.className; assert.isString(className, 'className'); const registry = this.registry; const adapter = registry.getAdapter(className); if (adapter === undefined) { problemConsumer(`No adapter for class '${className}'`); return false; } this.adapter = adapter; const adapterOptions = adapterOptionsSupplier(className, adapter.getClass(), adapter); assert.isArray(adapterOptions, 'adapterOptions'); //initialize adapter with options this.adapter.initialize.apply(this.adapter, adapterOptions); this.elementCount = 0; const buffer = this.buffer; //write class name buffer.writeUTF8String(className); this.headerAddress = buffer.position; //write empty header, to be written later buffer.writeUint32(0); //write placeholder element count buffer.writeUint32(0); this.startAddress = buffer.position; const componentType = adapter.getClass(); //determine if dictionary can be used this.__dictionaryEnabled = typeof componentType.prototype.hash === "function" && typeof componentType.prototype.equals === "function"; //clear dictionary this.dictionary.clear(); return true; } finalize() { const buffer = this.buffer; //remember current position const endAddress = buffer.position; buffer.position = this.headerAddress; const headerValues = []; headerValues[BinaryCollectionHeaderLayout.Dictionary] = this.__dictionaryEnabled ? 1 : 0; headerValues[BinaryCollectionHeaderLayout.Version] = this.adapter.getVersion(); const header = BinaryCollectionHeaderCodec.encode.apply(null, headerValues); //write header buffer.writeUint32(header); //write element count buffer.writeUint32(this.elementCount); //restore buffer position buffer.position = endAddress; //finalize current adapter this.adapter.finalize(); //clear dictionary this.dictionary.clear(); } /** * @template T * @param {T} value * @private */ writePlainValue(value) { this.adapter.serialize(this.buffer, value); } /** * @template T * @param {number} key * @param {T} value */ write(key, value) { const buffer = this.buffer; //write key buffer.writeUintVar(key); if (this.__dictionaryEnabled) { const dictionary = this.dictionary; let address = dictionary.get(value); if (address !== undefined) { if (address < 63) { buffer.writeUint8(1 | (address << 2)); } else { if (address < 16383) { buffer.writeUint16LE(2 | (address << 2)); } else if (address < 1073741823) { buffer.writeUint32LE(3 | (address << 2)); } else { throw new Error(`Address value is too high(=${address})`); } } } else { buffer.writeUint8(0); //original value address = buffer.position - this.startAddress; this.writePlainValue(value); dictionary.set(value, address); } } else { this.writePlainValue(value); } this.elementCount++; } }