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