@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
141 lines (123 loc) • 4.71 kB
JavaScript
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;
}
}