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
// 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<Entity>} */
this.entities = [];
/** @type {Set<Entity>} */
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
// 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.<string, Array<Entity>>} */
this.componentToEntity = newEmptyMap();
///** @type {Object.<string, Array<Entity>>} */
//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<number, Entity>}
*/
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<Entity>} 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<Entity>} 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<Entity>} 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);
}
}

View File

@ -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);