UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

213 lines (177 loc) • 6.26 kB
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;