1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

moved entityManager to sets, removed redundant iterations from gamesystemwithfilter

This commit is contained in:
dgs4349 2020-09-27 14:55:45 -04:00
parent ea23dfa996
commit 99b5729776
2 changed files with 80 additions and 77 deletions

View File

@ -13,6 +13,9 @@ const logger = createLogger("entity_manager");
// NOTICE: We use arrayDeleteValue instead of fastArrayDeleteValue since that does not preserve the order // 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 // This is slower but we need it for the street path generation
/** @typedef {number} EntityUid */
/** @typedef {string} ComponentId */
export class EntityManager extends BasicSerializableObject { export class EntityManager extends BasicSerializableObject {
constructor(root) { constructor(root) {
super(); super();
@ -20,8 +23,14 @@ export class EntityManager extends BasicSerializableObject {
/** @type {GameRoot} */ /** @type {GameRoot} */
this.root = root; this.root = root;
/** @type {Array<Entity>} */ /** @type {Set<Entity>} */
this.entities = []; this.entities = new Set();
/** @type {Map<EntityUid, Entity>} */
this.entitiesByUid = new Map();
/** @type {Map<ComponentId, Set<Entity>>} */
this.entitiesByComponent = new Map();
// We store a separate list with entities to destroy, since we don't destroy // We store a separate list with entities to destroy, since we don't destroy
// them instantly // 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 // Store a map from componentid to entities - This is used by the game system
// for faster processing // for faster processing
/** @type {Object.<string, Array<Entity>>} */ ///** @type {Object.<string, Array<Entity>>} */
this.componentToEntity = newEmptyMap(); //this.componentToEntity = newEmptyMap();
// Store the next uid to use // Store the next uid to use
this.nextUid = 10000; this.nextUid = 10000;
@ -48,7 +57,7 @@ export class EntityManager extends BasicSerializableObject {
} }
getStatsText() { getStatsText() {
return this.entities.length + " entities [" + this.destroyList.length + " to kill]"; return this.entities.size + " entities [" + this.destroyList.length + " to kill]";
} }
// Main update // Main update
@ -56,6 +65,19 @@ export class EntityManager extends BasicSerializableObject {
this.processDestroyList(); 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 * Registers a new entity
* @param {Entity} entity * @param {Entity} entity
@ -63,7 +85,7 @@ export class EntityManager extends BasicSerializableObject {
*/ */
registerEntity(entity, uid = null) { registerEntity(entity, uid = null) {
if (G_IS_DEV && !globalConfig.debug.disableSlowAsserts) { 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}`); 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); 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 // Register into the componentToEntity map
for (const componentId in entity.components) { for (const componentId in entity.components) {
if (entity.components[componentId]) { this.addToComponentMap(entity, componentId);
if (this.componentToEntity[componentId]) {
this.componentToEntity[componentId].push(entity);
} else {
this.componentToEntity[componentId] = [entity];
}
}
} }
// Give each entity a unique id
entity.uid = uid ? uid : this.generateUid();
entity.registered = true; entity.registered = true;
this.root.signals.entityAdded.dispatch(entity); this.root.signals.entityAdded.dispatch(entity);
@ -108,11 +126,8 @@ export class EntityManager extends BasicSerializableObject {
attachDynamicComponent(entity, component) { attachDynamicComponent(entity, component) {
entity.addComponent(component, true); entity.addComponent(component, true);
const componentId = /** @type {typeof Component} */ (component.constructor).getId(); const componentId = /** @type {typeof Component} */ (component.constructor).getId();
if (this.componentToEntity[componentId]) {
this.componentToEntity[componentId].push(entity); this.addToComponentMap(entity, componentId);
} else {
this.componentToEntity[componentId] = [entity];
}
this.root.signals.entityGotNewComponent.dispatch(entity); this.root.signals.entityGotNewComponent.dispatch(entity);
} }
@ -125,7 +140,7 @@ export class EntityManager extends BasicSerializableObject {
entity.removeComponent(component, true); entity.removeComponent(component, true);
const componentId = /** @type {typeof Component} */ (component.constructor).getId(); 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); this.root.signals.entityComponentRemoved.dispatch(entity);
} }
@ -136,18 +151,15 @@ export class EntityManager extends BasicSerializableObject {
* @returns {Entity} * @returns {Entity}
*/ */
findByUid(uid, errorWhenNotFound = true) { findByUid(uid, errorWhenNotFound = true) {
const arr = this.entities; const entity = this.entitiesByUid.get(uid);
for (let i = 0, len = arr.length; i < len; ++i) { if (entity) {
const entity = arr[i]; if (entity.queuedForDestroy || entity.destroyed) {
if (entity.uid === uid) { if (errorWhenNotFound) {
if (entity.queuedForDestroy || entity.destroyed) { logger.warn("Entity with UID", uid, "not found (destroyed)");
if (errorWhenNotFound) {
logger.warn("Entity with UID", uid, "not found (destroyed)");
}
return null;
} }
return entity; return null;
} }
return entity;
} }
if (errorWhenNotFound) { if (errorWhenNotFound) {
logger.warn("Entity with UID", uid, "not found"); logger.warn("Entity with UID", uid, "not found");
@ -162,15 +174,7 @@ export class EntityManager extends BasicSerializableObject {
* @returns {Map<number, Entity>} * @returns {Map<number, Entity>}
*/ */
getFrozenUidSearchMap() { getFrozenUidSearchMap() {
const result = new Map(); return this.entitiesByUid;
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;
} }
/** /**
@ -179,25 +183,30 @@ export class EntityManager extends BasicSerializableObject {
* @returns {Array<Entity>} entities * @returns {Array<Entity>} entities
*/ */
getAllWithComponent(componentHandle) { getAllWithComponent(componentHandle) {
return this.componentToEntity[componentHandle.getId()] || []; const set = this.entitiesByComponent.get(componentHandle.getId());
if (!set) return [];
else return [...set.values()];
} }
/** // Deprecated lol
* Return all of a given class. This is SLOW! // /**
* @param {object} entityClass // * Return all of a given class. This is SLOW!
* @returns {Array<Entity>} entities // * @param {object} entityClass
*/ // * @returns {Array<Entity>} entities
getAllOfClass(entityClass) { // */
// FIXME: Slow // getAllOfClass(entityClass) {
const result = []; // // FIXME: Slow
for (let i = 0; i < this.entities.length; ++i) { // // Fine! I will!
const entity = this.entities[i]; // const result = [];
if (entity instanceof entityClass) { // const entities = [...this.entities.values()];
result.push(entity); // for (let i = entities.length; i >= 0; --i) {
} // const entity = this.entities[i];
} // if (entity instanceof entityClass) {
return result; // result.push(entity);
} // }
// }
// return result;
// }
/** /**
* Unregisters all components of an entity from the component to entity mapping * Unregisters all components of an entity from the component to entity mapping
@ -205,20 +214,19 @@ export class EntityManager extends BasicSerializableObject {
*/ */
unregisterEntityComponents(entity) { unregisterEntityComponents(entity) {
for (const componentId in entity.components) { for (const componentId in entity.components) {
if (entity.components[componentId]) { const set = this.entitiesByComponent.get(componentId);
arrayDeleteValue(this.componentToEntity[componentId], entity); if (set) set.delete(entity);
}
} }
} }
// Processes the entities to destroy and actually destroys them // Processes the entities to destroy and actually destroys them
/* eslint-disable max-statements */ /* eslint-disable max-statements */
processDestroyList() { 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]; const entity = this.destroyList[i];
// Remove from entities list // Remove from entities list
arrayDeleteValue(this.entities, entity); this.entities.delete(entity);
// Remove from componentToEntity list // Remove from componentToEntity list
this.unregisterEntityComponents(entity); this.unregisterEntityComponents(entity);
@ -247,12 +255,8 @@ export class EntityManager extends BasicSerializableObject {
return; return;
} }
if (this.destroyList.indexOf(entity) < 0) { this.destroyList.push(entity);
this.destroyList.push(entity); entity.queuedForDestroy = true;
entity.queuedForDestroy = true; this.root.signals.entityQueuedForDestroy.dispatch(entity);
this.root.signals.entityQueuedForDestroy.dispatch(entity);
} else {
assert(false, "Trying to destroy entity twice");
}
} }
} }

View File

@ -26,6 +26,7 @@ export class GameSystemWithFilter extends GameSystem {
this.allEntitiesSet = new Set(); this.allEntitiesSet = new Set();
this.allEntitiesArray = []; this.allEntitiesArray = [];
this.allEntitiesArrayIsOutdated = true; this.allEntitiesArrayIsOutdated = true;
this.entitiesQueuedToDelete = [];
this.root.signals.entityAdded.add(this.internalPushEntityIfMatching, this); this.root.signals.entityAdded.add(this.internalPushEntityIfMatching, this);
this.root.signals.entityGotNewComponent.add(this.internalReconsiderEntityToAdd, this); this.root.signals.entityGotNewComponent.add(this.internalReconsiderEntityToAdd, this);
@ -95,13 +96,11 @@ export class GameSystemWithFilter extends GameSystem {
refreshCaches() { refreshCaches() {
//this.allEntities.sort((a, b) => a.uid - b.uid); //this.allEntities.sort((a, b) => a.uid - b.uid);
// Remove all entities which are queued for destroy // Remove all entities which are queued for destroy
if (this.entitiesQueuedToDelete.length > 0) {
for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { for (let i = this.entitiesQueuedToDelete.length - 1; i >= 0; --i) {
const entity = this.allEntitiesArray[i]; this.allEntitiesSet.delete(this.entitiesQueuedToDelete[i]);
if (entity.queuedForDestroy || entity.destroyed) {
this.allEntitiesSet.delete(this.allEntitiesArray[i]);
fastArrayDelete(this.allEntitiesArray, i);
} }
this.entitiesQueuedToDelete = [];
} }
// called here in case a delete executed mid frame // called here in case a delete executed mid frame
@ -135,7 +134,7 @@ export class GameSystemWithFilter extends GameSystem {
*/ */
internalPopEntityIfMatching(entity) { internalPopEntityIfMatching(entity) {
if (this.root.bulkOperationRunning) { if (this.root.bulkOperationRunning) {
// We do this in refreshCaches afterwards this.entitiesQueuedToDelete.push(entity);
return; return;
} }
this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity); this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity);