@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
264 lines (192 loc) • 6.32 kB
JavaScript
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.klass, 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.klass;
//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.version;
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++;
}
}