@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
213 lines (177 loc) • 6.26 kB
JavaScript
import { array_copy_unique } from "../../core/collection/array/array_copy_unique.js";
import { array_push_if_unique } from "../../core/collection/array/array_push_if_unique.js";
import { noop } from "../../core/function/noop.js";
import ObservedValue from "../../core/model/ObservedValue.js";
import { ResourceAccessKind } from "../../core/model/ResourceAccessKind.js";
/**
* Base class to extend ECS systems from.
* A System defines some behavior over a tuple of components.
* Override base methods as needed to achieve desired behavior. The system already offers {@link update} and {@link fixedUpdate} methods for time-based simulation.
* If you have event-driven requirements - make use of {@link link} and {@link unlink} methods, as well as messaging facilities of {@link EntityComponentDataset}.
*
* @template C
*
* @example
* class FallDamageSystem{
* dependencies = [Health, Physics]
*
* listeners = []; // internal storage
*
* link(health, physics, entity){
* const listener = () => health.value -= 10; // receive damage on fall
* physics.onContact.add(listener);
* this.listeners[entity] = listener; // remember for later
* }
*
* unlink(health, physics, entity){
* const listener = this.listeners[entity];
* physics.onContact.remove(listener);
* delete this.listeners[entity]; // cleanup
* }
* }
*/
export class System {
/**
* Reference to the attached {@link EntityManager}, usually set during {@link startup}
* Useful for gaining access to other Systems, via {@link EntityManager.getSystem} as well as the associated dataset via {@link EntityManager.dataset}
* @protected
* @type {EntityManager}
*/
entityManager = null;
/**
* Managed internally by {@link EntityManager}, do not modify manually
* @readonly
* @type {ObservedValue.<SystemState>}
*/
state = new ObservedValue(SystemState.INITIAL);
/**
* Components which have to be present before the system links an entity
* NOTE: do not modify this while the system is running
* @type {Array}
*/
dependencies = [];
/**
* Component types that are used internally by the system and how they are used
* Main benefit of doing so is twofold:
* - Helps the engine figure out the best execution order for system to make sure that updates propagate as quickly as possible
* - Declaring this helps EntityManager to ensure that all relevant component types are properly registered for the system
*
* NOTE: specifying this is optional. The engine will still work.
* NOTE: do not modify this while the system is running
* @type {ResourceAccessSpecification[]}
*/
components_used = [];
/**
* @returns {Array} Component classes
*/
get referenced_components() {
const result = [];
array_copy_unique(this.dependencies, 0, result, result.length, this.dependencies.length);
const used = this.components_used;
const use_count = used.length;
for (let i = 0; i < use_count; i++) {
const ref = used[i];
array_push_if_unique(result, ref.resource);
}
return result;
}
/**
* @template T
* @param {T} Klass
* @return {number}
*/
getAccessForComponent(Klass) {
let result = 0;
const used = this.components_used;
const use_count = used.length;
for (let i = 0; i < use_count; i++) {
const ref = used[i];
if (ref.resource === Klass) {
result |= ref.access;
break;
}
}
const dependencies = this.dependencies;
const dependency_count = dependencies.length;
for (let i = 0; i < dependency_count; i++) {
const dependency = dependencies[i];
if (dependency === Klass) {
// at the very least it's going to read
result |= ResourceAccessKind.Read;
break;
}
}
return result;
}
/**
*
* @param {EntityManager} entityManager
* @param {function} readyCallback
* @param {function} errorCallback
*/
startup(entityManager, readyCallback, errorCallback) {
this.entityManager = entityManager;
readyCallback();
}
/**
*
* @param {EntityManager} entityManager
* @param {function} readyCallback
* @param {function} errorCallback
*/
shutdown(entityManager, readyCallback, errorCallback) {
readyCallback();
}
link(component, entity) {
}
unlink(component, entity) {
}
}
/**
* @readonly
* @type {boolean}
*/
System.prototype.isSystem = true;
/**
* Fixed update function, every step happens with the same exact time increment
* useful for systems that must have a fixed time step to be predictable and stable, such as physics
* @param {number} timeDelta in seconds
*/
System.prototype.fixedUpdate = noop; // by assigning NO-OP we enable a simple check, whether running the update would be useful
/**
* This update is generally synchronized with the render loop
* @param {number} timeDelta Time in seconds
*/
System.prototype.update = noop; // by assigning NO-OP we enable a simple check, whether running the update would be useful
Object.defineProperties(System.prototype, {
/**
* @deprecated
*/
componentClass: {
configurable: true,
/**
* @deprecated
* @returns {Class<C>}
*/
get() {
console.warn(`componentClass property is deprecated and should not be used`);
return null;
}
}
});
/**
* @readonly
* @enum {number}
*/
export const SystemState = {
INITIAL: 0,
STARTING: 1,
RUNNING: 2,
STOPPING: 3,
STOPPED: 4
}
/**
* @readonly
* @deprecated use {@link SystemState} directly instead
*/
System.State = SystemState;