From 99b57297767f91ff83e2c8ce3d535078baf090bf Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 14:55:45 -0400 Subject: [PATCH] moved entityManager to sets, removed redundant iterations from gamesystemwithfilter --- src/js/game/entity_manager.js | 144 +++++++++++++------------ src/js/game/game_system_with_filter.js | 13 ++- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/src/js/game/entity_manager.js b/src/js/game/entity_manager.js index 613ed12d..580b2d5d 100644 --- a/src/js/game/entity_manager.js +++ b/src/js/game/entity_manager.js @@ -13,6 +13,9 @@ const logger = createLogger("entity_manager"); // NOTICE: We use arrayDeleteValue instead of fastArrayDeleteValue since that does not preserve the order // This is slower but we need it for the street path generation +/** @typedef {number} EntityUid */ +/** @typedef {string} ComponentId */ + export class EntityManager extends BasicSerializableObject { constructor(root) { super(); @@ -20,8 +23,14 @@ export class EntityManager extends BasicSerializableObject { /** @type {GameRoot} */ this.root = root; - /** @type {Array} */ - this.entities = []; + /** @type {Set} */ + this.entities = new Set(); + + /** @type {Map} */ + this.entitiesByUid = new Map(); + + /** @type {Map>} */ + this.entitiesByComponent = new Map(); // We store a separate list with entities to destroy, since we don't destroy // them instantly @@ -30,8 +39,8 @@ export class EntityManager extends BasicSerializableObject { // Store a map from componentid to entities - This is used by the game system // for faster processing - /** @type {Object.>} */ - this.componentToEntity = newEmptyMap(); + ///** @type {Object.>} */ + //this.componentToEntity = newEmptyMap(); // Store the next uid to use this.nextUid = 10000; @@ -48,7 +57,7 @@ export class EntityManager extends BasicSerializableObject { } getStatsText() { - return this.entities.length + " entities [" + this.destroyList.length + " to kill]"; + return this.entities.size + " entities [" + this.destroyList.length + " to kill]"; } // Main update @@ -56,6 +65,19 @@ export class EntityManager extends BasicSerializableObject { this.processDestroyList(); } + /** + * @param {Entity} entity + * @param {ComponentId} componentId + */ + addToComponentMap(entity, componentId) { + let set; + if ((set = this.entitiesByComponent.get(componentId))) { + set.add(entity); + } else { + this.entitiesByComponent.set(componentId, new Set([entity])); + } + } + /** * Registers a new entity * @param {Entity} entity @@ -63,7 +85,7 @@ export class EntityManager extends BasicSerializableObject { */ registerEntity(entity, uid = null) { if (G_IS_DEV && !globalConfig.debug.disableSlowAsserts) { - assert(this.entities.indexOf(entity) < 0, `RegisterEntity() called twice for entity ${entity}`); + assert(!this.entities.has(entity), `RegisterEntity() called twice for entity ${entity}`); } assert(!entity.destroyed, `Attempting to register destroyed entity ${entity}`); @@ -72,21 +94,17 @@ export class EntityManager extends BasicSerializableObject { assert(uid >= 0 && uid < Number.MAX_SAFE_INTEGER, "Invalid uid passed: " + uid); } - this.entities.push(entity); + // Give each entity a unique id + entity.uid = uid ? uid : this.generateUid(); + + this.entities.add(entity); + this.entitiesByUid.set(uid, entity); // Register into the componentToEntity map for (const componentId in entity.components) { - if (entity.components[componentId]) { - if (this.componentToEntity[componentId]) { - this.componentToEntity[componentId].push(entity); - } else { - this.componentToEntity[componentId] = [entity]; - } - } + this.addToComponentMap(entity, componentId); } - // Give each entity a unique id - entity.uid = uid ? uid : this.generateUid(); entity.registered = true; this.root.signals.entityAdded.dispatch(entity); @@ -108,11 +126,8 @@ export class EntityManager extends BasicSerializableObject { attachDynamicComponent(entity, component) { entity.addComponent(component, true); const componentId = /** @type {typeof Component} */ (component.constructor).getId(); - if (this.componentToEntity[componentId]) { - this.componentToEntity[componentId].push(entity); - } else { - this.componentToEntity[componentId] = [entity]; - } + + this.addToComponentMap(entity, componentId); this.root.signals.entityGotNewComponent.dispatch(entity); } @@ -125,7 +140,7 @@ export class EntityManager extends BasicSerializableObject { entity.removeComponent(component, true); const componentId = /** @type {typeof Component} */ (component.constructor).getId(); - fastArrayDeleteValue(this.componentToEntity[componentId], entity); + this.entitiesByComponent.get(componentId).delete(entity); this.root.signals.entityComponentRemoved.dispatch(entity); } @@ -136,18 +151,15 @@ export class EntityManager extends BasicSerializableObject { * @returns {Entity} */ findByUid(uid, errorWhenNotFound = true) { - const arr = this.entities; - for (let i = 0, len = arr.length; i < len; ++i) { - const entity = arr[i]; - if (entity.uid === uid) { - if (entity.queuedForDestroy || entity.destroyed) { - if (errorWhenNotFound) { - logger.warn("Entity with UID", uid, "not found (destroyed)"); - } - return null; + const entity = this.entitiesByUid.get(uid); + if (entity) { + if (entity.queuedForDestroy || entity.destroyed) { + if (errorWhenNotFound) { + logger.warn("Entity with UID", uid, "not found (destroyed)"); } - return entity; + return null; } + return entity; } if (errorWhenNotFound) { logger.warn("Entity with UID", uid, "not found"); @@ -162,15 +174,7 @@ export class EntityManager extends BasicSerializableObject { * @returns {Map} */ getFrozenUidSearchMap() { - const result = new Map(); - const array = this.entities; - for (let i = 0, len = array.length; i < len; ++i) { - const entity = array[i]; - if (!entity.queuedForDestroy && !entity.destroyed) { - result.set(entity.uid, entity); - } - } - return result; + return this.entitiesByUid; } /** @@ -179,25 +183,30 @@ export class EntityManager extends BasicSerializableObject { * @returns {Array} entities */ getAllWithComponent(componentHandle) { - return this.componentToEntity[componentHandle.getId()] || []; + const set = this.entitiesByComponent.get(componentHandle.getId()); + if (!set) return []; + else return [...set.values()]; } - /** - * Return all of a given class. This is SLOW! - * @param {object} entityClass - * @returns {Array} entities - */ - getAllOfClass(entityClass) { - // FIXME: Slow - const result = []; - for (let i = 0; i < this.entities.length; ++i) { - const entity = this.entities[i]; - if (entity instanceof entityClass) { - result.push(entity); - } - } - return result; - } + // Deprecated lol + // /** + // * Return all of a given class. This is SLOW! + // * @param {object} entityClass + // * @returns {Array} entities + // */ + // getAllOfClass(entityClass) { + // // FIXME: Slow + // // Fine! I will! + // const result = []; + // const entities = [...this.entities.values()]; + // for (let i = entities.length; i >= 0; --i) { + // const entity = this.entities[i]; + // if (entity instanceof entityClass) { + // result.push(entity); + // } + // } + // return result; + // } /** * Unregisters all components of an entity from the component to entity mapping @@ -205,20 +214,19 @@ export class EntityManager extends BasicSerializableObject { */ unregisterEntityComponents(entity) { for (const componentId in entity.components) { - if (entity.components[componentId]) { - arrayDeleteValue(this.componentToEntity[componentId], entity); - } + const set = this.entitiesByComponent.get(componentId); + if (set) set.delete(entity); } } // Processes the entities to destroy and actually destroys them /* eslint-disable max-statements */ processDestroyList() { - for (let i = 0; i < this.destroyList.length; ++i) { + for (let i = this.destroyList.length - 1; i >= 0; --i) { const entity = this.destroyList[i]; // Remove from entities list - arrayDeleteValue(this.entities, entity); + this.entities.delete(entity); // Remove from componentToEntity list this.unregisterEntityComponents(entity); @@ -247,12 +255,8 @@ export class EntityManager extends BasicSerializableObject { return; } - if (this.destroyList.indexOf(entity) < 0) { - this.destroyList.push(entity); - entity.queuedForDestroy = true; - this.root.signals.entityQueuedForDestroy.dispatch(entity); - } else { - assert(false, "Trying to destroy entity twice"); - } + this.destroyList.push(entity); + entity.queuedForDestroy = true; + this.root.signals.entityQueuedForDestroy.dispatch(entity); } } diff --git a/src/js/game/game_system_with_filter.js b/src/js/game/game_system_with_filter.js index 23126166..d76fdcf3 100644 --- a/src/js/game/game_system_with_filter.js +++ b/src/js/game/game_system_with_filter.js @@ -26,6 +26,7 @@ export class GameSystemWithFilter extends GameSystem { this.allEntitiesSet = new Set(); this.allEntitiesArray = []; this.allEntitiesArrayIsOutdated = true; + this.entitiesQueuedToDelete = []; this.root.signals.entityAdded.add(this.internalPushEntityIfMatching, this); this.root.signals.entityGotNewComponent.add(this.internalReconsiderEntityToAdd, this); @@ -95,13 +96,11 @@ export class GameSystemWithFilter extends GameSystem { refreshCaches() { //this.allEntities.sort((a, b) => a.uid - b.uid); // Remove all entities which are queued for destroy - - for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { - const entity = this.allEntitiesArray[i]; - if (entity.queuedForDestroy || entity.destroyed) { - this.allEntitiesSet.delete(this.allEntitiesArray[i]); - fastArrayDelete(this.allEntitiesArray, i); + if (this.entitiesQueuedToDelete.length > 0) { + for (let i = this.entitiesQueuedToDelete.length - 1; i >= 0; --i) { + this.allEntitiesSet.delete(this.entitiesQueuedToDelete[i]); } + this.entitiesQueuedToDelete = []; } // called here in case a delete executed mid frame @@ -135,7 +134,7 @@ export class GameSystemWithFilter extends GameSystem { */ internalPopEntityIfMatching(entity) { if (this.root.bulkOperationRunning) { - // We do this in refreshCaches afterwards + this.entitiesQueuedToDelete.push(entity); return; } this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity);