UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

146 lines (121 loc) 3.98 kB
import { assert } from "../../core/assert.js"; import { BitSet } from "../../core/binary/BitSet.js"; /** * Signaling component query. * Enables definition of a component class tuple, which will then be observed; for any entity that completes the tuple - a notification will be dispatched, same for any broken tuples. * see {@link EntityComponentDataset.addObserver}/{@link EntityComponentDataset.removeObserver}. * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class EntityObserver { /** * * @type {BitSet} */ componentMask = new BitSet(); /** * Mapping from component index to position in array of observed component types, * this is used to build arguments for callbacks * @type {number[]} */ componentIndexMapping = []; /** * Currently attached dataset. `null` when not attached. * @type {EntityComponentDataset|null} */ dataset = null; /** * * @param {Array} componentTypes * @param {function(components:Array)} completedCallback * @param {function(components:Array)} brokenCallback * @param {*} [thisArg=undefined] will assume {@link this} value inside callbacks * @constructor */ constructor( componentTypes, completedCallback, brokenCallback, thisArg ) { assert.isArray(componentTypes, 'componentTypes must be an array, instead was something else'); assert.isFunction(completedCallback, 'completedCallback'); assert.isFunction(brokenCallback, 'brokenCallback'); const numComponentTypes = componentTypes.length; if (numComponentTypes < 1) { throw new Error(`Observer has to have at least 1 component types to watch, instead was given ${numComponentTypes}`); } /** * @type {number} */ this.componentTypeCount = numComponentTypes; /** * * @type {function(Array)} */ this.callbackComplete = completedCallback; /** * * @type {function(Array)} */ this.callbackBroken = brokenCallback; /** * * @type {Array} */ this.componentTypes = componentTypes; /** * * @type {*} */ this.thisArg = thisArg; } /** * * @param {Array} componentTypeMap */ build(componentTypeMap) { let i; this.componentIndexMapping = []; this.componentMask.reset(); for (i = 0; i < this.componentTypeCount; i++) { const componentType = this.componentTypes[i]; const componentTypeIndex = componentTypeMap.indexOf(componentType); if (componentTypeIndex === -1) { throw new Error(`Component type[${i}] was not found in the supplied map. Observer is not compatible.`); } this.componentMask.set(componentTypeIndex, true); this.componentIndexMapping[componentTypeIndex] = i; } } /** * A shortcut for {@link EntityComponentDataset.addObserver} * @param {EntityComponentDataset} dataset */ connect(dataset) { if (this.dataset !== null) { throw new Error("Already attached to a dataset, disconnect first"); } dataset.addObserver(this, true); } /** * * @return {boolean} */ get isConnected() { return this.dataset === null; } /** * A shortcut for {@link EntityComponentDataset.removeObserver} */ disconnect() { if (this.dataset === null) { // not attached return; } //de-register updates this.dataset.removeObserver(this); this.dataset = null; } }