UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

129 lines (110 loc) 3.7 kB
import { assert } from "../../../core/assert.js"; /** * Registry that maps `type_id ↔ SimAction subclass`, with a small per-type * object pool to recycle action instances (avoids GC pressure on the hot path). * * Wire format: `type_id` is a `uint8`. The cap of 256 distinct action types is * deliberate — if you need more, your design probably wants a generic * `MutateComponentAction` rather than a bespoke action per case. * * Registration is order-sensitive: `type_id` is assigned in registration order * starting from 0. To keep `type_id` stable across versions, register actions * in a fixed order at engine init. * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class SimActionRegistry { #classes_by_id; #pools; constructor() { /** * @type {Function[]} * @private */ this.#classes_by_id = []; /** * Per-type instance pool. `pools[type_id]` is an array of available SimAction instances. * @type {SimAction[][]} * @private */ this.#pools = []; /** * Maximum pool size per type. Beyond this, released instances are dropped. * @type {number} */ this.max_pool_size = 64; } /** * Register a SimAction subclass and assign it a stable `type_id`. * Returns the assigned id. * * @param {Function} klass * @returns {number} assigned type_id */ register(klass) { assert.isFunction(klass, 'klass'); assert.notEqual(klass.type_id, undefined, "klass must inherit static type_id from SimAction"); const type_id = this.#classes_by_id.length; if (type_id > 0xFF) { throw new Error(`SimActionRegistry: at most 256 action types are supported, tried to register ${type_id + 1}`); } klass.type_id = type_id; this.#classes_by_id[type_id] = klass; this.#pools[type_id] = []; return type_id; } /** * Look up an action class by type_id. Returns undefined if unknown. * * @param {number} type_id * @returns {Function|undefined} */ klass_for(type_id) { assert.isNonNegativeInteger(type_id, 'type_id'); return this.#classes_by_id[type_id]; } /** * Acquire a recycled instance of the given action class, or create a fresh * one if the pool is empty. The returned instance has had `reset()` called. * * @param {Function|{type_id:number}} klass * @returns {SimAction} */ acquire(klass) { assert.isFunction(klass, 'klass'); const pool = this.#pools[klass.type_id]; if (pool === undefined) { throw new Error(`SimActionRegistry.acquire: ${klass.name} is not registered`); } if (pool.length > 0) { const instance = pool.pop(); instance.reset(); return instance; } return new klass(); } /** * Release an instance back to its pool. Drops on the floor if the pool is full. * * @param {SimAction} instance */ release(instance) { assert.ok(instance && instance.isSimAction, 'release: instance must be a SimAction'); const type_id = instance.constructor.type_id; const pool = this.#pools[type_id]; if (pool === undefined) { return; // unregistered — let it GC } if (pool.length < this.max_pool_size) { pool.push(instance); } } /** * Number of registered action types. * @returns {number} */ type_count() { return this.#classes_by_id.length; } }