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