UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

226 lines (175 loc) • 6.92 kB
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;