UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

141 lines (123 loc) 4.71 kB
import { assert } from "../../../core/assert.js"; /** * Maps replicated component classes to compact `uint8` type IDs and to their * replication adapters. Sits on top of the engine's existing * {@link BinarySerializationRegistry}, which keys adapters by class name (string). * * Two reasons this exists separately: * 1. The action-log wire format wants 1-byte type tags, not strings. * 2. The "set of components that get replicated" is a strict subset of "all * classes that have a binary adapter" (e.g. save-game components that we * don't replicate). Registering for replication is an explicit opt-in. * * Registration is order-sensitive: `type_id` is assigned in registration order * starting from 0. To keep `type_id` stable across versions, register in a * fixed order at engine init. * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class ReplicatedComponentRegistry { #binary_registry; #classes_by_id; #id_by_class; #adapters_by_id; /** * @param {BinarySerializationRegistry} binary_registry */ constructor(binary_registry) { /** * @type {BinarySerializationRegistry} * @private */ this.#binary_registry = binary_registry; /** * Component classes indexed by assigned type_id. * @type {Function[]} * @private */ this.#classes_by_id = []; /** * `class -> type_id` lookup. Class identity is the key. * @type {Map<Function, number>} * @private */ this.#id_by_class = new Map(); /** * `type_id -> adapter` cache so we don't re-hash the className every lookup. * @type {BinaryClassSerializationAdapter[]} * @private */ this.#adapters_by_id = []; } /** * Register a component class for replication. Looks up the adapter from the * underlying binary registry by class typeName and assigns a stable type_id. * * @param {Function} component_class must have `typeName` static and be registered * in the binary serialization registry * @returns {number} assigned type_id */ register(component_class) { assert.isFunction(component_class, 'component_class'); if (this.#id_by_class.has(component_class)) { throw new Error(`ReplicatedComponentRegistry.register: ${component_class.typeName ?? component_class.name} already registered`); } const type_name = component_class.typeName; if (typeof type_name !== "string") { throw new Error(`ReplicatedComponentRegistry.register: ${component_class.name} has no static .typeName`); } const adapter = this.#binary_registry.getAdapter(type_name); if (adapter === undefined) { throw new Error(`ReplicatedComponentRegistry.register: no adapter registered for '${type_name}' in BinarySerializationRegistry`); } const type_id = this.#classes_by_id.length; if (type_id > 0xFF) { throw new Error(`ReplicatedComponentRegistry: at most 256 component types are supported, tried to register ${type_id + 1}`); } this.#classes_by_id[type_id] = component_class; this.#adapters_by_id[type_id] = adapter; this.#id_by_class.set(component_class, type_id); return type_id; } /** * @param {Function} component_class * @returns {number} type_id, or -1 if unregistered */ type_id_of(component_class) { assert.isFunction(component_class, 'component_class'); const id = this.#id_by_class.get(component_class); return id === undefined ? -1 : id; } /** * @param {number} type_id * @returns {Function|undefined} */ class_of(type_id) { assert.isNonNegativeInteger(type_id, 'type_id'); return this.#classes_by_id[type_id]; } /** * @param {number} type_id * @returns {BinaryClassSerializationAdapter|undefined} */ adapter_for_id(type_id) { assert.isNonNegativeInteger(type_id, 'type_id'); return this.#adapters_by_id[type_id]; } /** * @param {Function} component_class * @returns {BinaryClassSerializationAdapter|undefined} */ adapter_for_class(component_class) { assert.isFunction(component_class, 'component_class'); const id = this.#id_by_class.get(component_class); return id === undefined ? undefined : this.#adapters_by_id[id]; } /** * @returns {number} */ type_count() { return this.#classes_by_id.length; } }