@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
346 lines (263 loc) • 8.51 kB
JavaScript
import { assert } from "../../../../../core/assert.js";
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
import { IllegalStateException } from "../../../../../core/fsm/exceptions/IllegalStateException.js";
import { returnEmptyArray } from "../../../../../core/function/returnEmptyArray.js";
import { objectKeyByValue } from "../../../../../core/model/object/objectKeyByValue.js";
import { executeBinaryClassUpgraderChain } from "../executeBinaryClassUpgraderChain.js";
import { BinaryCollectionHeaderCodec, BinaryCollectionHeaderLayout } from "./BinaryCollectionHeaderCodec.js";
/**
*
* @enum {number}
*/
const State = {
Initial: 0,
Ready: 1
};
/**
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class BinaryCollectionDeSerializer {
/**
* @private
* @type {BinarySerializationRegistry}
*/
registry = null;
/**
* @private
* @type {BinaryBuffer}
*/
buffer = null;
/**
*
* @type {boolean}
* @private
*/
__dictionaryEnabled = false;
/**
*
* @type {boolean}
* @private
*/
__upgradeRequired = false;
/**
* @private
* @type {number}
*/
elementCount = 0;
/**
* @private
* @type {number}
*/
elementIndex = 0;
/**
* @private
* @type {number}
*/
startAddress = 0;
/**
* @private
* @type {BinaryClassSerializationAdapter}
*/
adapter = null;
/**
* @private
* @type {BinaryClassUpgrader[]}
*/
upgraders = null;
/**
*
* @type {BinaryBuffer}
* @private
*/
__upgradeBuffer0 = new BinaryBuffer();
/**
*
* @type {BinaryBuffer}
* @private
*/
__upgradeBuffer1 = new BinaryBuffer();
/**
* @private
* @type {State}
*/
state = State.Initial;
/**
*
* @param {BinarySerializationRegistry} registry
*/
setRegistry(registry) {
assert.notEqual(registry, undefined, 'registry is undefined');
assert.notEqual(registry, null, 'registry is null');
this.registry = registry;
}
/**
*
* @returns {BinarySerializationRegistry}
*/
getRegistry() {
return this.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;
}
/**
*
* @returns {number}
*/
getElementIndex() {
return this.elementIndex;
}
/**
* @template
* @returns {Class<T>|null}
*/
getElementClass() {
const adapter = this.adapter;
if (adapter === null) {
return null;
}
return adapter.klass;
}
/**
*
* @param {function(name:string, klass: Class, adapter:BinaryClassSerializationAdapter):[]} [adapterOptionsSupplier]
*/
initialize({ adapterOptionsSupplier = returnEmptyArray } = {}) {
assert.isFunction(adapterOptionsSupplier, 'adapterOptionsSupplier');
if (this.state !== State.Initial) {
throw new IllegalStateException(`Expected state to be Initial, instead was ${objectKeyByValue(State, this.state)}`);
}
const buffer = this.buffer;
const registry = this.registry;
assert.notEqual(buffer, null, 'buffer is null');
assert.notEqual(registry, null, 'registry is null');
const className = buffer.readUTF8String();
const adapter = registry.getAdapter(className);
if (adapter === undefined) {
throw new Error(`No serialization adapter found for '${className}'`);
}
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);
const header = buffer.readUint32();
const headerValues = [];
BinaryCollectionHeaderCodec.decode(header, headerValues);
//read serialized version
const version = headerValues[BinaryCollectionHeaderLayout.Version];
//read flags
this.__dictionaryEnabled = headerValues[BinaryCollectionHeaderLayout.Dictionary] !== 0;
//read element count
this.elementCount = buffer.readUint32();
//set element index to 0
this.elementIndex = 0;
//check if data needs to be upgraded
if (version > adapter.version) {
throw new Error(`${className} data version is ${version}, which is greater than the registered serialization adapter version(=${(adapter.version)})`);
}
if (version < adapter.version) {
this.__upgradeRequired = true;
//data needs to be upgraded
const upgradersChain = registry.getUpgradersChain(className, version, adapter.version);
if (upgradersChain === null) {
throw new Error(`No upgrade chain exists for class '${className}' from version ${version} to current adapter version ${(adapter.version)}`);
}
this.upgraders = upgradersChain;
} else {
this.__upgradeRequired = false;
}
//
this.__upgradeBuffer0.position = 0;
this.__upgradeBuffer1.position = 0;
this.startAddress = this.buffer.position;
//Update internal state
this.state = State.Ready;
}
finalize() {
if (this.state !== State.Ready) {
throw new IllegalStateException(`Expected state to be Ready, instead was ${objectKeyByValue(State, this.state)}`);
}
//finalize current adapter
this.adapter.finalize();
//Update internal state
this.state = State.Initial;
}
/**
* @private
* @returns T
*/
readPlainValue() {
let buffer = this.buffer;
if (this.__upgradeRequired) {
//binary representation requires an upgrade
buffer = executeBinaryClassUpgraderChain(this.upgraders, buffer, this.__upgradeBuffer0, this.__upgradeBuffer1);
}
const Klass = this.adapter.klass;
const value = new Klass();
this.adapter.deserialize(buffer, value);
return value;
}
/**
* @template T
* @returns {{value: T, key: number}}
*/
read() {
const buffer = this.buffer;
//read key
const key = buffer.readUintVar();
let value = null;
if (this.__dictionaryEnabled) {
//read record header
const header0 = buffer.readUint8();
if (header0 === 0) {
//plain record
value = this.readPlainValue();
} else {
//dictionary record
const headerType = header0 & 0x3;
let header;
if (headerType === 1) {
header = header0;
} else if (headerType === 2) {
//16 bit header
buffer.position--;
header = buffer.readUint16LE();
} else if (headerType === 3) {
buffer.position--;
header = buffer.readUint32LE();
}
const offset = header >> 2;
//read address
const address = offset + this.startAddress;
const recordEnd = buffer.position;
buffer.position = address;
value = this.readPlainValue();
//restore position
buffer.position = recordEnd;
}
} else {
value = this.readPlainValue();
}
this.elementIndex++;
return {
key,
value
};
}
}