mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Improve mass deletion performance
This commit is contained in:
parent
b7c773a70e
commit
5bdf6386a1
@ -1,229 +1,221 @@
|
||||
/* typehints:start */
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { Component } from "./component";
|
||||
/* typehints:end */
|
||||
|
||||
import { GameRoot } from "./root";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { enumDirectionToVector, enumDirectionToAngle } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { EntityComponentStorage } from "./entity_components";
|
||||
import { Loader } from "../core/loader";
|
||||
import { drawRotatedSprite } from "../core/draw_utils";
|
||||
import { gComponentRegistry } from "../core/global_registries";
|
||||
|
||||
export class Entity extends BasicSerializableObject {
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* Handle to the global game root
|
||||
*/
|
||||
this.root = root;
|
||||
|
||||
/**
|
||||
* The components of the entity
|
||||
*/
|
||||
this.components = new EntityComponentStorage();
|
||||
|
||||
/**
|
||||
* Whether this entity was registered on the @see EntityManager so far
|
||||
*/
|
||||
this.registered = false;
|
||||
|
||||
/**
|
||||
* On which layer this entity is
|
||||
* @type {Layer}
|
||||
*/
|
||||
this.layer = "regular";
|
||||
|
||||
/**
|
||||
* Internal entity unique id, set by the @see EntityManager
|
||||
*/
|
||||
this.uid = 0;
|
||||
|
||||
/* typehints:start */
|
||||
|
||||
/**
|
||||
* Stores if this entity is destroyed, set by the @see EntityManager
|
||||
* @type {boolean} */
|
||||
this.destroyed;
|
||||
|
||||
/**
|
||||
* Stores if this entity is queued to get destroyed in the next tick
|
||||
* of the @see EntityManager
|
||||
* @type {boolean} */
|
||||
this.queuedForDestroy;
|
||||
|
||||
/**
|
||||
* Stores the reason why this entity was destroyed
|
||||
* @type {string} */
|
||||
this.destroyReason;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
|
||||
static getId() {
|
||||
return "Entity";
|
||||
}
|
||||
|
||||
/**
|
||||
* @see BasicSerializableObject.getSchema
|
||||
* @returns {import("../savegame/serialization").Schema}
|
||||
*/
|
||||
static getSchema() {
|
||||
return {
|
||||
uid: types.uint,
|
||||
components: types.keyValueMap(types.objData(gComponentRegistry), false),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone of this entity without contents
|
||||
*/
|
||||
duplicateWithoutContents() {
|
||||
const clone = new Entity(this.root);
|
||||
for (const key in this.components) {
|
||||
clone.components[key] = this.components[key].duplicateWithoutContents();
|
||||
}
|
||||
clone.layer = this.layer;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal destroy callback
|
||||
*/
|
||||
internalDestroyCallback() {
|
||||
assert(!this.destroyed, "Can not destroy entity twice");
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new component, only possible until the entity is registered on the entity manager,
|
||||
* after that use @see EntityManager.addDynamicComponent
|
||||
* @param {Component} componentInstance
|
||||
* @param {boolean} force Used by the entity manager. Internal parameter, do not change
|
||||
*/
|
||||
addComponent(componentInstance, force = false) {
|
||||
if (!force && this.registered) {
|
||||
this.root.entityMgr.attachDynamicComponent(this, componentInstance);
|
||||
return;
|
||||
}
|
||||
assert(force || !this.registered, "Entity already registered, use EntityManager.addDynamicComponent");
|
||||
const id = /** @type {typeof Component} */ (componentInstance.constructor).getId();
|
||||
assert(!this.components[id], "Component already present");
|
||||
this.components[id] = componentInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given component, only possible until the entity is registered on the entity manager,
|
||||
* after that use @see EntityManager.removeDynamicComponent
|
||||
* @param {typeof Component} componentClass
|
||||
* @param {boolean} force
|
||||
*/
|
||||
removeComponent(componentClass, force = false) {
|
||||
if (!force && this.registered) {
|
||||
this.root.entityMgr.removeDynamicComponent(this, componentClass);
|
||||
return;
|
||||
}
|
||||
assert(
|
||||
force || !this.registered,
|
||||
"Entity already registered, use EntityManager.removeDynamicComponent"
|
||||
);
|
||||
const id = componentClass.getId();
|
||||
assert(this.components[id], "Component does not exist on entity");
|
||||
delete this.components[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the entity, to override use @see Entity.drawImpl
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawDebugOverlays(parameters) {
|
||||
const context = parameters.context;
|
||||
const staticComp = this.components.StaticMapEntity;
|
||||
|
||||
if (G_IS_DEV && staticComp && globalConfig.debug.showEntityBounds) {
|
||||
if (staticComp) {
|
||||
const transformed = staticComp.getTileSpaceBounds();
|
||||
context.strokeStyle = "rgba(255, 0, 0, 0.5)";
|
||||
context.lineWidth = 2;
|
||||
// const boundsSize = 20;
|
||||
context.beginPath();
|
||||
context.rect(
|
||||
transformed.x * globalConfig.tileSize,
|
||||
transformed.y * globalConfig.tileSize,
|
||||
transformed.w * globalConfig.tileSize,
|
||||
transformed.h * globalConfig.tileSize
|
||||
);
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
if (G_IS_DEV && staticComp && globalConfig.debug.showAcceptorEjectors) {
|
||||
const ejectorComp = this.components.ItemEjector;
|
||||
|
||||
if (ejectorComp) {
|
||||
const ejectorSprite = Loader.getSprite("sprites/debug/ejector_slot.png");
|
||||
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||
const slot = ejectorComp.slots[i];
|
||||
const slotTile = staticComp.localTileToWorld(slot.pos);
|
||||
const direction = staticComp.localDirectionToWorld(slot.direction);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction]);
|
||||
|
||||
context.globalAlpha = slot.item ? 1 : 0.2;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: ejectorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
}
|
||||
const acceptorComp = this.components.ItemAcceptor;
|
||||
|
||||
if (acceptorComp) {
|
||||
const acceptorSprite = Loader.getSprite("sprites/misc/acceptor_slot.png");
|
||||
for (let i = 0; i < acceptorComp.slots.length; ++i) {
|
||||
const slot = acceptorComp.slots[i];
|
||||
const slotTile = staticComp.localTileToWorld(slot.pos);
|
||||
for (let k = 0; k < slot.directions.length; ++k) {
|
||||
const direction = staticComp.localDirectionToWorld(slot.directions[k]);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction] + 180);
|
||||
context.globalAlpha = 0.4;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: acceptorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
// this.drawImpl(parameters);
|
||||
}
|
||||
|
||||
///// Helper interfaces
|
||||
|
||||
///// Interface to override by subclasses
|
||||
|
||||
/**
|
||||
* override, should draw the entity
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawImpl(parameters) {
|
||||
abstract;
|
||||
}
|
||||
}
|
||||
/* typehints:start */
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { Component } from "./component";
|
||||
/* typehints:end */
|
||||
|
||||
import { GameRoot } from "./root";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { enumDirectionToVector, enumDirectionToAngle } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { EntityComponentStorage } from "./entity_components";
|
||||
import { Loader } from "../core/loader";
|
||||
import { drawRotatedSprite } from "../core/draw_utils";
|
||||
import { gComponentRegistry } from "../core/global_registries";
|
||||
|
||||
export class Entity extends BasicSerializableObject {
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* Handle to the global game root
|
||||
*/
|
||||
this.root = root;
|
||||
|
||||
/**
|
||||
* The components of the entity
|
||||
*/
|
||||
this.components = new EntityComponentStorage();
|
||||
|
||||
/**
|
||||
* Whether this entity was registered on the @see EntityManager so far
|
||||
*/
|
||||
this.registered = false;
|
||||
|
||||
/**
|
||||
* On which layer this entity is
|
||||
* @type {Layer}
|
||||
*/
|
||||
this.layer = "regular";
|
||||
|
||||
/**
|
||||
* Internal entity unique id, set by the @see EntityManager
|
||||
*/
|
||||
this.uid = 0;
|
||||
|
||||
/* typehints:start */
|
||||
|
||||
/**
|
||||
* Stores if this entity is destroyed, set by the @see EntityManager
|
||||
* @type {boolean} */
|
||||
this.destroyed;
|
||||
|
||||
/**
|
||||
* Stores if this entity is queued to get destroyed in the next tick
|
||||
* of the @see EntityManager
|
||||
* @type {boolean} */
|
||||
this.queuedForDestroy;
|
||||
|
||||
/**
|
||||
* Stores the reason why this entity was destroyed
|
||||
* @type {string} */
|
||||
this.destroyReason;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
|
||||
static getId() {
|
||||
return "Entity";
|
||||
}
|
||||
|
||||
/**
|
||||
* @see BasicSerializableObject.getSchema
|
||||
* @returns {import("../savegame/serialization").Schema}
|
||||
*/
|
||||
static getSchema() {
|
||||
return {
|
||||
uid: types.uint,
|
||||
components: types.keyValueMap(types.objData(gComponentRegistry), false),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone of this entity without contents
|
||||
*/
|
||||
duplicateWithoutContents() {
|
||||
const clone = new Entity(this.root);
|
||||
for (const key in this.components) {
|
||||
clone.components[key] = this.components[key].duplicateWithoutContents();
|
||||
}
|
||||
clone.layer = this.layer;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new component, only possible until the entity is registered on the entity manager,
|
||||
* after that use @see EntityManager.addDynamicComponent
|
||||
* @param {Component} componentInstance
|
||||
* @param {boolean} force Used by the entity manager. Internal parameter, do not change
|
||||
*/
|
||||
addComponent(componentInstance, force = false) {
|
||||
if (!force && this.registered) {
|
||||
this.root.entityMgr.attachDynamicComponent(this, componentInstance);
|
||||
return;
|
||||
}
|
||||
assert(force || !this.registered, "Entity already registered, use EntityManager.addDynamicComponent");
|
||||
const id = /** @type {typeof Component} */ (componentInstance.constructor).getId();
|
||||
assert(!this.components[id], "Component already present");
|
||||
this.components[id] = componentInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given component, only possible until the entity is registered on the entity manager,
|
||||
* after that use @see EntityManager.removeDynamicComponent
|
||||
* @param {typeof Component} componentClass
|
||||
* @param {boolean} force
|
||||
*/
|
||||
removeComponent(componentClass, force = false) {
|
||||
if (!force && this.registered) {
|
||||
this.root.entityMgr.removeDynamicComponent(this, componentClass);
|
||||
return;
|
||||
}
|
||||
assert(
|
||||
force || !this.registered,
|
||||
"Entity already registered, use EntityManager.removeDynamicComponent"
|
||||
);
|
||||
const id = componentClass.getId();
|
||||
assert(this.components[id], "Component does not exist on entity");
|
||||
delete this.components[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the entity, to override use @see Entity.drawImpl
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawDebugOverlays(parameters) {
|
||||
const context = parameters.context;
|
||||
const staticComp = this.components.StaticMapEntity;
|
||||
|
||||
if (G_IS_DEV && staticComp && globalConfig.debug.showEntityBounds) {
|
||||
if (staticComp) {
|
||||
const transformed = staticComp.getTileSpaceBounds();
|
||||
context.strokeStyle = "rgba(255, 0, 0, 0.5)";
|
||||
context.lineWidth = 2;
|
||||
// const boundsSize = 20;
|
||||
context.beginPath();
|
||||
context.rect(
|
||||
transformed.x * globalConfig.tileSize,
|
||||
transformed.y * globalConfig.tileSize,
|
||||
transformed.w * globalConfig.tileSize,
|
||||
transformed.h * globalConfig.tileSize
|
||||
);
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
if (G_IS_DEV && staticComp && globalConfig.debug.showAcceptorEjectors) {
|
||||
const ejectorComp = this.components.ItemEjector;
|
||||
|
||||
if (ejectorComp) {
|
||||
const ejectorSprite = Loader.getSprite("sprites/debug/ejector_slot.png");
|
||||
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||
const slot = ejectorComp.slots[i];
|
||||
const slotTile = staticComp.localTileToWorld(slot.pos);
|
||||
const direction = staticComp.localDirectionToWorld(slot.direction);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction]);
|
||||
|
||||
context.globalAlpha = slot.item ? 1 : 0.2;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: ejectorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
}
|
||||
const acceptorComp = this.components.ItemAcceptor;
|
||||
|
||||
if (acceptorComp) {
|
||||
const acceptorSprite = Loader.getSprite("sprites/misc/acceptor_slot.png");
|
||||
for (let i = 0; i < acceptorComp.slots.length; ++i) {
|
||||
const slot = acceptorComp.slots[i];
|
||||
const slotTile = staticComp.localTileToWorld(slot.pos);
|
||||
for (let k = 0; k < slot.directions.length; ++k) {
|
||||
const direction = staticComp.localDirectionToWorld(slot.directions[k]);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction] + 180);
|
||||
context.globalAlpha = 0.4;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: acceptorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
// this.drawImpl(parameters);
|
||||
}
|
||||
|
||||
///// Helper interfaces
|
||||
|
||||
///// Interface to override by subclasses
|
||||
|
||||
/**
|
||||
* override, should draw the entity
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawImpl(parameters) {
|
||||
abstract;
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +155,24 @@ export class EntityManager extends BasicSerializableObject {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map which gives a mapping from UID to Entity.
|
||||
* This map is not updated.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entities having the given component
|
||||
* @param {typeof Component} componentHandle
|
||||
@ -206,7 +224,7 @@ export class EntityManager extends BasicSerializableObject {
|
||||
this.unregisterEntityComponents(entity);
|
||||
|
||||
entity.registered = false;
|
||||
entity.internalDestroyCallback();
|
||||
entity.destroyed = true;
|
||||
|
||||
this.root.signals.entityDestroyed.dispatch(entity);
|
||||
}
|
||||
|
@ -88,15 +88,16 @@ 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 = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
if (entity.queuedForDestroy || entity.destroyed) {
|
||||
this.allEntities.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.allEntities.sort((a, b) => a.uid - b.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,6 +48,9 @@ export class HUDMassSelector extends BaseHUDPart {
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
onEntityDestroyed(entity) {
|
||||
if (this.root.bulkOperationRunning) {
|
||||
return;
|
||||
}
|
||||
this.selectedUids.delete(entity.uid);
|
||||
}
|
||||
|
||||
@ -90,14 +93,30 @@ export class HUDMassSelector extends BaseHUDPart {
|
||||
|
||||
doDelete() {
|
||||
const entityUids = Array.from(this.selectedUids);
|
||||
for (let i = 0; i < entityUids.length; ++i) {
|
||||
const uid = entityUids[i];
|
||||
const entity = this.root.entityMgr.findByUid(uid);
|
||||
if (!this.root.logic.tryDeleteBuilding(entity)) {
|
||||
logger.error("Error in mass delete, could not remove building");
|
||||
this.selectedUids.delete(uid);
|
||||
|
||||
// Build mapping from uid to entity
|
||||
/**
|
||||
* @type {Map<number, Entity>}
|
||||
*/
|
||||
const mapUidToEntity = this.root.entityMgr.getFrozenUidSearchMap();
|
||||
|
||||
this.root.logic.performBulkOperation(() => {
|
||||
for (let i = 0; i < entityUids.length; ++i) {
|
||||
const uid = entityUids[i];
|
||||
const entity = mapUidToEntity.get(uid);
|
||||
if (!entity) {
|
||||
logger.error("Entity not found by uid:", uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.root.logic.tryDeleteBuilding(entity)) {
|
||||
logger.error("Error in mass delete, could not remove building");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear uids later
|
||||
this.selectedUids = new Set();
|
||||
}
|
||||
|
||||
startCopy() {
|
||||
|
Loading…
Reference in New Issue
Block a user