UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

223 lines (177 loc) • 5.56 kB
import { EventType } from "../EventType.js"; import { AbstractContextSystem } from "../system/AbstractContextSystem.js"; import { SystemEntityContext } from "../system/SystemEntityContext.js"; import { ParentEntity } from "./ParentEntity.js"; /** * * @type {number[]|Uint32Array} */ const loop_checker_array = new Uint32Array(1024); /** * * @param {number} value * @param {number} index_limit * @returns {boolean} */ function check_loop(value, index_limit) { for (let i = 0; i < index_limit; i++) { if (loop_checker_array[i] === value) { return true; } } return false; } /** * * @param {number} limit * @returns {string} */ function genLoopString(limit) { let r = ""; for (let i = 0; i < limit; i++) { if (i > 0) { r += " -> "; } r += loop_checker_array[i]; } return r; } class Context extends SystemEntityContext { link() { super.link(); const ecd = this.getDataset(); /** * * @type {ParentEntity} */ const cParent = this.components[0]; ecd.addEntityEventListener(cParent.entity, EventType.EntityRemoved, this.handleParentEntityRemoval, this); } handleParentEntityRemoval() { // delete this entity as well this.getDataset().removeEntity(this.entity); } unlink() { super.unlink(); const ecd = this.getDataset(); /** * * @type {ParentEntity} */ const cParent = this.components[0]; ecd.removeEntityEventListener(cParent.entity, EventType.EntityRemoved, this.handleParentEntityRemoval, this); } } export class ParentEntitySystem extends AbstractContextSystem { constructor() { super(Context); this.dependencies = [ParentEntity]; } /** * Find root entity given some entity with a {@link ParentEntity} component attached * @param {EntityComponentDataset} ecd * @param {number} entity * @returns {number} */ static findRoot(ecd, entity) { let count = 0; let p = entity; while (true) { loop_checker_array[count++] = p; const parent = ParentEntitySystem.findParentEntity(ecd, p); if (parent === -1) { break; } if (check_loop(parent, count)) { throw new Error(`Cyclic reference encountered: ${genLoopString(count)}`); } p = parent; } return p; } /** * @param {EntityComponentDataset} ecd * @param {number} child_entity * @param {number} ancestor_entity * @returns {boolean} */ static isAncestorOf(ecd, child_entity, ancestor_entity) { let loop_count = 0; let p = child_entity; while (true) { if (p === ancestor_entity) { return true; } loop_checker_array[loop_count++] = p; p = ParentEntitySystem.findParentEntity(ecd, p); if (p === -1) { break; } if (check_loop(p, loop_count)) { throw new Error(`Cyclic reference encountered: ${genLoopString(loop_count)}`); } } // reached the root, ancestor not encountered return false; } /** * @param {EntityComponentDataset} ecd * @param {number} entity * @returns {number} */ static findParentEntity(ecd, entity) { const parent = ecd.getComponent(entity, ParentEntity); if (parent === undefined) { return -1; } return parent.entity; } /** * * @param {number[]} result * @param {number} result_offset * @param {EntityComponentDataset} ecd * @param {number} entity * @returns {number} */ static findChildrenOf(result, result_offset, ecd, entity) { let result_cursor = result_offset; ecd.traverseComponents(ParentEntity, (p, e) => { if (p.entity === entity) { result[result_cursor++] = e; } }); return result_cursor - result_offset; } /** * * @param {EntityComponentDataset} ecd * @param {number} root_entity * @param {function(number, EntityComponentDataset)} callback * @param {*} [thisArg] */ static traverse(ecd, root_entity, callback, thisArg) { traverse(ecd, root_entity, callback, thisArg); } } /** * * @param {EntityComponentDataset} ecd * @param {number} root_entity * @param {function(number, EntityComponentDataset)} callback * @param {*} [thisArg] * @param {number[]} path keep track of where we came from, this is used to detect cycles */ function traverse(ecd, root_entity, callback, thisArg, path = []) { callback.call(thisArg, root_entity, ecd); const children = []; const child_count = ParentEntitySystem.findChildrenOf(children, 0, ecd, root_entity); for (let i = 0; i < child_count; i++) { const entity = children[i]; if (path.includes(entity)) { console.warn(`Loop in entity hierarchy detected: ${path.join('->')}`); continue; } traverse(ecd, entity, callback, thisArg, path.concat(entity)); } }