@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
226 lines (175 loc) • 6.92 kB
JavaScript
import { assert } from "../../../../core/assert.js";
import { Graph } from "../../../../core/graph/v2/Graph.js";
/**
* Contains serializers for various data types as well as data upgraders which enable support for serialization format changes
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class BinarySerializationRegistry {
/**
* @readonly
* @private
* @type {Map<string, Graph<BinaryClassUpgrader>>}
*/
upgraders = new Map();
/**
* @readonly
* @private
* @type {Map<string, BinaryClassSerializationAdapter>}
*/
serializers = new Map();
/**
*
* @param {BinaryClassSerializationAdapter[]} adapters
*/
registerAdapters(adapters) {
adapters.forEach(adapter => {
this.registerAdapter(adapter);
});
}
/**
*
* @param {BinaryClassSerializationAdapter} adapter
* @param {string} [className] if not specified, will be taken from the {@link BinaryClassSerializationAdapter.klass.typeName}
* @returns {boolean} true if added, false if adapter already existed
* @throws {Error} if `className` could not be inferred
*/
registerAdapter(adapter, className) {
assert.defined(adapter, 'adapter');
assert.ok(adapter.isBinaryClassSerializationAdapter, 'adapter is not a BinaryClassSerializationAdapter');
let _className = className;
if (_className === undefined) {
const klass = adapter.klass;
const typeName = klass.typeName;
if (typeof typeName === "string") {
_className = typeName;
} else {
throw new Error(`className not specified, could not infer class name from the class itself`);
}
}
assert.isString(_className, 'className');
if (this.serializers.has(_className)) {
// a serializer already exists
console.warn(`Serializer for class '${_className}' already exists, ignoring request`);
return false;
}
this.serializers.set(_className, adapter);
return true;
}
/**
*
* @param {string} className
* @returns {BinaryClassSerializationAdapter|undefined}
*/
removeAdapter(className) {
assert.isString(className, 'className');
const adapter = this.serializers.get(className);
if (adapter !== undefined) {
this.serializers.delete(className);
}
return adapter;
}
/**
*
* @param {string} className
* @returns {BinaryClassSerializationAdapter|undefined}
*/
getAdapter(className) {
assert.isString(className, 'className');
return this.serializers.get(className);
}
/**
*
* @param {string} className
* @param {BinaryClassUpgrader} upgrader
* @returns {boolean} true iff added, false if already exists
*/
registerUpgrader(className, upgrader) {
assert.isString(className, 'className');
assert.defined(upgrader, 'upgrader');
assert.notNull(upgrader, 'upgrader');
assert.equal(upgrader.isBinaryClassUpgrader, true, 'upgrader.isBinaryClassUpgrader !== true');
if (upgrader.getStartVersion() === upgrader.getTargetVersion()) {
throw new Error(`Upgrader for '${className}' start(=${upgrader.getStartVersion()}) and target(=${upgrader.getTargetVersion()}) versions are the same, must be different`);
}
if (upgrader.getStartVersion() > upgrader.getTargetVersion()) {
console.warn(`Possible error in '${className}' upgrader code, target version(=${upgrader.getTargetVersion()}) < start version(=${upgrader.getStartVersion()})`);
}
let classUpgraders = this.upgraders.get(className);
if (classUpgraders === undefined) {
classUpgraders = new Graph();
this.upgraders.set(className, classUpgraders);
}
if (classUpgraders.hasNode(upgrader)) {
// upgrader already registered
return false;
}
classUpgraders.addNode(upgrader);
// find other upgraders that would connect to this upgrader
classUpgraders.traverseNodes(u => {
if (u === upgrader) {
return;
}
if (u.getStartVersion() === upgrader.getTargetVersion()) {
classUpgraders.createEdge(u, upgrader);
} else if (u.getTargetVersion() === upgrader.getStartVersion()) {
classUpgraders.createEdge(upgrader, u);
}
});
return true;
}
/**
*
* @param {string} className
* @param {number} startVersion
* @param {number} goalVersion
* @returns {BinaryClassUpgrader[]|null} sequential shortest chain of upgraders or null if no valid chain exists
*/
getUpgradersChain(className, startVersion, goalVersion) {
assert.isString(className, 'className');
assert.isNumber(startVersion, 'startVersion');
assert.isNumber(goalVersion, 'goalVersion');
if (startVersion === goalVersion) {
//already at the goal version
return [];
}
const classUpgraders = this.upgraders.get(className);
if (classUpgraders === undefined) {
throw new Error(`No upgraders available for class '${className}', upgrade is required from from version ${startVersion} to ${goalVersion}`);
}
const starts = [];
const goals = [];
classUpgraders.traverseNodes(n => {
if (n.getStartVersion() === startVersion) {
starts.push(n);
}
if (n.getTargetVersion() === goalVersion) {
goals.push(n);
}
});
let bestPath = null;
let bestPathLength = Number.POSITIVE_INFINITY;
for (let s = 0, sL = starts.length; s < sL; s++) {
const start = starts[s];
for (let g = 0, gL = goals.length; g < gL; g++) {
const goal = goals[g];
const path = classUpgraders.findPath(start, goal);
if (path !== null) {
const pathLength = path.length;
if (bestPathLength > pathLength) {
//new best path found
bestPath = path;
bestPathLength = pathLength;
}
}
}
}
return bestPath;
}
}
/**
* @readonly
* @type {boolean}
*/
BinarySerializationRegistry.prototype.isBinarySerializationRegistry = true;