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