From b7cb6b606dccdd4c644a9e3be0255e224eb4e1fc Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 10:10:06 -0400 Subject: [PATCH 01/10] moved all gamesystemwithfilter arrays to sets, significant fps boost? --- src/js/game/game_system_with_filter.js | 59 +++++++++++++++++-------- src/js/game/systems/belt_reader.js | 7 ++- src/js/game/systems/constant_signal.js | 7 ++- src/js/game/systems/hub.js | 16 ++++--- src/js/game/systems/item_acceptor.js | 7 ++- src/js/game/systems/item_ejector.js | 14 ++++-- src/js/game/systems/item_processor.js | 8 ++-- src/js/game/systems/lever.js | 8 ++-- src/js/game/systems/logic_gate.js | 7 ++- src/js/game/systems/miner.js | 7 ++- src/js/game/systems/storage.js | 7 ++- src/js/game/systems/underground_belt.js | 7 ++- 12 files changed, 106 insertions(+), 48 deletions(-) diff --git a/src/js/game/game_system_with_filter.js b/src/js/game/game_system_with_filter.js index 7b1ffbf0..eb1807d2 100644 --- a/src/js/game/game_system_with_filter.js +++ b/src/js/game/game_system_with_filter.js @@ -21,9 +21,11 @@ export class GameSystemWithFilter extends GameSystem { /** * All entities which match the current components - * @type {Array} + * @type {Set} */ - this.allEntities = []; + this.allEntitiesSet = new Set(); + this.allEntitiesArray = []; + this.allEntitiesArrayIsOutdated = true; this.root.signals.entityAdded.add(this.internalPushEntityIfMatching, this); this.root.signals.entityGotNewComponent.add(this.internalReconsiderEntityToAdd, this); @@ -34,6 +36,17 @@ export class GameSystemWithFilter extends GameSystem { this.root.signals.bulkOperationFinished.add(this.refreshCaches, this); } + getUpdateEntitiesArray() { + if (!this.allEntitiesArrayIsOutdated) { + return this.allEntitiesArray; + } + + this.allEntitiesArray = [...this.allEntitiesSet.values()]; + + this.allEntitiesArrayIsOutdated = true; + return this.allEntitiesArray; + } + /** * @param {Entity} entity */ @@ -44,7 +57,7 @@ export class GameSystemWithFilter extends GameSystem { } } - assert(this.allEntities.indexOf(entity) < 0, "entity already in list: " + entity); + assert(!this.allEntitiesSet.has(entity), "entity already in list: " + entity); this.internalRegisterEntity(entity); } @@ -53,7 +66,7 @@ export class GameSystemWithFilter extends GameSystem { * @param {Entity} entity */ internalCheckEntityAfterComponentRemoval(entity) { - if (this.allEntities.indexOf(entity) < 0) { + if (!this.allEntitiesSet.has(entity)) { // Entity wasn't interesting anyways return; } @@ -61,7 +74,8 @@ export class GameSystemWithFilter extends GameSystem { for (let i = 0; i < this.requiredComponentIds.length; ++i) { if (!entity.components[this.requiredComponentIds[i]]) { // Entity is not interesting anymore - arrayDeleteValue(this.allEntities, entity); + //arrayDeleteValue(this.allEntities, entity); + this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity); } } } @@ -76,20 +90,30 @@ export class GameSystemWithFilter extends GameSystem { return; } } - if (this.allEntities.indexOf(entity) >= 0) { + if (this.allEntitiesSet.has(entity)) { return; } this.internalRegisterEntity(entity); } 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 - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + // for (let i = 0; i < this.allEntities.length; ++i) { + // const entity = this.allEntities[i]; + // if (entity.queuedForDestroy || entity.destroyed) { + // this.allEntities.splice(i, 1); + // } + // } + + for ( + let arr = [...this.allEntitiesSet.values()], i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { if (entity.queuedForDestroy || entity.destroyed) { - this.allEntities.splice(i, 1); + this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity); } } } @@ -106,12 +130,12 @@ export class GameSystemWithFilter extends GameSystem { * @param {Entity} entity */ internalRegisterEntity(entity) { - this.allEntities.push(entity); + this.allEntitiesSet.add(entity); - if (this.root.gameInitialized && !this.root.bulkOperationRunning) { - // Sort entities by uid so behaviour is predictable - this.allEntities.sort((a, b) => a.uid - b.uid); - } + // if (this.root.gameInitialized && !this.root.bulkOperationRunning) { + // // Sort entities by uid so behaviour is predictable + // this.allEntities.sort((a, b) => a.uid - b.uid); + // } } /** @@ -123,9 +147,6 @@ export class GameSystemWithFilter extends GameSystem { // We do this in refreshCaches afterwards return; } - const index = this.allEntities.indexOf(entity); - if (index >= 0) { - arrayDelete(this.allEntities, index); - } + this.allEntitiesSet.delete(entity); } } diff --git a/src/js/game/systems/belt_reader.js b/src/js/game/systems/belt_reader.js index abddd999..df780bcb 100644 --- a/src/js/game/systems/belt_reader.js +++ b/src/js/game/systems/belt_reader.js @@ -12,9 +12,12 @@ export class BeltReaderSystem extends GameSystemWithFilter { const now = this.root.time.now(); const minimumTime = now - globalConfig.readerAnalyzeIntervalSeconds; const minimumTimeForThroughput = now - 1; - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const readerComp = entity.components.BeltReader; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index 93417ef5..135501ce 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -19,8 +19,11 @@ export class ConstantSignalSystem extends GameSystemWithFilter { update() { // Set signals - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const pinsComp = entity.components.WiredPins; const signalComp = entity.components.ConstantSignal; pinsComp.slots[0].value = signalComp.signal; diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index 2270f941..917ca240 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -25,15 +25,21 @@ export class HubSystem extends GameSystemWithFilter { * @param {DrawParameters} parameters */ draw(parameters) { - for (let i = 0; i < this.allEntities.length; ++i) { - this.drawEntity(parameters, this.allEntities[i]); + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { + this.drawEntity(parameters, entity); } } update() { - for (let i = 0; i < this.allEntities.length; ++i) { - // Set hub goal - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const pinsComp = entity.components.WiredPins; pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition( this.root.hubGoals.currentGoal.definition diff --git a/src/js/game/systems/item_acceptor.js b/src/js/game/systems/item_acceptor.js index 6d6fec77..1d43f3cf 100644 --- a/src/js/game/systems/item_acceptor.js +++ b/src/js/game/systems/item_acceptor.js @@ -18,8 +18,11 @@ export class ItemAcceptorSystem extends GameSystemWithFilter { this.root.hubGoals.getBeltBaseSpeed() * globalConfig.itemSpacingOnBelts; // * 2 because its only a half tile - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const aceptorComp = entity.components.ItemAcceptor; const animations = aceptorComp.itemConsumptionAnimations; diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 925dcc2e..386d3157 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -76,8 +76,11 @@ export class ItemEjectorSystem extends GameSystemWithFilter { } // Try to find acceptors for every ejector - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { this.recomputeSingleEntityCache(entity); } } @@ -195,8 +198,11 @@ export class ItemEjectorSystem extends GameSystemWithFilter { } // Go over all cache entries - for (let i = 0; i < this.allEntities.length; ++i) { - const sourceEntity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, sourceEntity; + (sourceEntity = arr[i]) && i >= 0; + --i + ) { const sourceEjectorComp = sourceEntity.components.ItemEjector; if (!sourceEjectorComp.enabled) { continue; diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index d58aa697..7c112d04 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -22,9 +22,11 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } update() { - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; - + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const processorComp = entity.components.ItemProcessor; const ejectorComp = entity.components.ItemEjector; diff --git a/src/js/game/systems/lever.js b/src/js/game/systems/lever.js index 75b6cf28..4710ed09 100644 --- a/src/js/game/systems/lever.js +++ b/src/js/game/systems/lever.js @@ -14,9 +14,11 @@ export class LeverSystem extends GameSystemWithFilter { } update() { - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; - + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const leverComp = entity.components.Lever; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js index 3bfc20cd..ea873a41 100644 --- a/src/js/game/systems/logic_gate.js +++ b/src/js/game/systems/logic_gate.js @@ -28,8 +28,11 @@ export class LogicGateSystem extends GameSystemWithFilter { } update() { - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const logicComp = entity.components.LogicGate; const slotComp = entity.components.WiredPins; diff --git a/src/js/game/systems/miner.js b/src/js/game/systems/miner.js index 94f2791e..9036d185 100644 --- a/src/js/game/systems/miner.js +++ b/src/js/game/systems/miner.js @@ -36,8 +36,11 @@ export class MinerSystem extends GameSystemWithFilter { miningSpeed *= 100; } - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const minerComp = entity.components.Miner; // Reset everything on recompute diff --git a/src/js/game/systems/storage.js b/src/js/game/systems/storage.js index 5a2b57bb..6eda7b1f 100644 --- a/src/js/game/systems/storage.js +++ b/src/js/game/systems/storage.js @@ -26,8 +26,11 @@ export class StorageSystem extends GameSystemWithFilter { } update() { - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const storageComp = entity.components.Storage; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/underground_belt.js b/src/js/game/systems/underground_belt.js index 90d29e50..68a47d61 100644 --- a/src/js/game/systems/underground_belt.js +++ b/src/js/game/systems/underground_belt.js @@ -255,8 +255,11 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { const delta = this.root.dynamicTickrate.deltaSeconds; - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const undergroundComp = entity.components.UndergroundBelt; const pendingItems = undergroundComp.pendingItems; From 54e4f45bcd9950df87ef3793ab55d68cb69ec29a Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 10:29:31 -0400 Subject: [PATCH 02/10] last minute fix --- src/js/game/systems/filter.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/game/systems/filter.js b/src/js/game/systems/filter.js index a6442b41..0db3fdb3 100644 --- a/src/js/game/systems/filter.js +++ b/src/js/game/systems/filter.js @@ -20,8 +20,11 @@ export class FilterSystem extends GameSystemWithFilter { const requiredProgress = 1 - progress; - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for ( + let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; + (entity = arr[i]) && i >= 0; + --i + ) { const filterComp = entity.components.Filter; const ejectorComp = entity.components.ItemEjector; From 6f76246df88397b7afded131b5c66180f5767d97 Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 12:23:57 -0400 Subject: [PATCH 03/10] fixing silly mistakes in iteration --- src/js/core/utils.js | 38 +++++++++++++++++++++++++ src/js/game/game_system_with_filter.js | 32 ++++++--------------- src/js/game/systems/belt_reader.js | 4 +-- src/js/game/systems/constant_signal.js | 4 +-- src/js/game/systems/filter.js | 4 +-- src/js/game/systems/hub.js | 8 +++--- src/js/game/systems/item_acceptor.js | 4 +-- src/js/game/systems/item_ejector.js | 10 ++++--- src/js/game/systems/item_processor.js | 4 +-- src/js/game/systems/lever.js | 4 +-- src/js/game/systems/logic_gate.js | 9 +++--- src/js/game/systems/miner.js | 4 +-- src/js/game/systems/storage.js | 4 +-- src/js/game/systems/underground_belt.js | 4 +-- 14 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/js/core/utils.js b/src/js/core/utils.js index a469a7a0..ec2f0593 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -670,3 +670,41 @@ export function safeModulo(n, m) { export function smoothPulse(time) { return Math.sin(time * 4) * 0.5 + 0.5; } + +let logIntervals = {}; +const intervalStyle = "color: grey; font-style: inherit"; +const keyStyle = "color: purple; font-style: italic"; +const revertStyle = "color: inherit; font-style: inherit"; + +export function logInterval(key, frames, message, ...args) { + let interval = logIntervals[key] || 0; + if (++interval > frames) { + console.log( + `%clogInterval [%c${key}%c]: \t%c` + message, + intervalStyle, + keyStyle, + intervalStyle, + revertStyle, + ...args + ); + interval = 0; + } + logIntervals[key] = interval; +} + +export function dirInterval(key, frames, object, premessage, ...args) { + let interval = logIntervals[key] || 0; + if (++interval > frames) { + console.log( + `%cdirInterval [%c${key}%c]: \t%c` + (premessage || ""), + intervalStyle, + keyStyle, + intervalStyle, + revertStyle, + ...args + ); + console.dir(object); + interval = 0; + } + logIntervals[key] = interval; +} diff --git a/src/js/game/game_system_with_filter.js b/src/js/game/game_system_with_filter.js index 5c647194..013502ab 100644 --- a/src/js/game/game_system_with_filter.js +++ b/src/js/game/game_system_with_filter.js @@ -5,7 +5,7 @@ import { Entity } from "./entity"; import { GameRoot } from "./root"; import { GameSystem } from "./game_system"; -import { arrayDelete, arrayDeleteValue } from "../core/utils"; +import { arrayDelete, arrayDeleteValue, fastArrayDelete } from "../core/utils"; export class GameSystemWithFilter extends GameSystem { /** @@ -36,15 +36,8 @@ export class GameSystemWithFilter extends GameSystem { this.root.signals.bulkOperationFinished.add(this.refreshCaches, this); } - getUpdateEntitiesArray() { - if (!this.allEntitiesArrayIsOutdated) { - return this.allEntitiesArray; - } - + tryUpdateEntitiesArray() { this.allEntitiesArray = [...this.allEntitiesSet.values()]; - - this.allEntitiesArrayIsOutdated = true; - return this.allEntitiesArray; } /** @@ -98,22 +91,15 @@ 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); - // } - // } - for ( - let arr = [...this.allEntitiesSet.values()], i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; - --i - ) { + this.tryUpdateEntitiesArray(); + + for (let i = 0; i < this.allEntitiesArray.length; ++i) { + const entity = this.allEntitiesArray[i]; if (entity.queuedForDestroy || entity.destroyed) { - this.allEntitiesArrayIsOutdated = this.allEntitiesSet.delete(entity); + this.allEntitiesSet.delete(this.allEntitiesArray[i]); + fastArrayDelete(this.allEntitiesArray, i); } } } @@ -131,7 +117,7 @@ export class GameSystemWithFilter extends GameSystem { */ internalRegisterEntity(entity) { this.allEntitiesSet.add(entity); - this.allEntitiesArrayIsOutdated = true; + this.allEntitiesArray.push(entity); // if (this.root.gameInitialized && !this.root.bulkOperationRunning) { // // Sort entities by uid so behaviour is predictable diff --git a/src/js/game/systems/belt_reader.js b/src/js/game/systems/belt_reader.js index 777b0a31..7d93e5cc 100644 --- a/src/js/game/systems/belt_reader.js +++ b/src/js/game/systems/belt_reader.js @@ -14,8 +14,8 @@ export class BeltReaderSystem extends GameSystemWithFilter { const minimumTimeForThroughput = now - 1; for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const readerComp = entity.components.BeltReader; diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index 135501ce..4803347e 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -20,8 +20,8 @@ export class ConstantSignalSystem extends GameSystemWithFilter { update() { // Set signals for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/filter.js b/src/js/game/systems/filter.js index 0db3fdb3..b9cd3f2d 100644 --- a/src/js/game/systems/filter.js +++ b/src/js/game/systems/filter.js @@ -21,8 +21,8 @@ export class FilterSystem extends GameSystemWithFilter { const requiredProgress = 1 - progress; for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const filterComp = entity.components.Filter; diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index 917ca240..56442e39 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -26,8 +26,8 @@ export class HubSystem extends GameSystemWithFilter { */ draw(parameters) { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { this.drawEntity(parameters, entity); @@ -36,8 +36,8 @@ export class HubSystem extends GameSystemWithFilter { update() { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/item_acceptor.js b/src/js/game/systems/item_acceptor.js index 8f97020d..2a071b5e 100644 --- a/src/js/game/systems/item_acceptor.js +++ b/src/js/game/systems/item_acceptor.js @@ -40,8 +40,8 @@ export class ItemAcceptorSystem extends GameSystemWithFilter { this.accumulatedTicksWhileInMapOverview = 0; for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const aceptorComp = entity.components.ItemAcceptor; diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index e626ef5a..c4d668ee 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -3,6 +3,7 @@ import { DrawParameters } from "../../core/draw_parameters"; import { createLogger } from "../../core/logging"; import { Rectangle } from "../../core/rectangle"; import { StaleAreaDetector } from "../../core/stale_area_detector"; +import { dirInterval } from "../../core/utils"; import { enumDirection, enumDirectionToVector } from "../../core/vector"; import { BaseItem } from "../base_item"; import { BeltComponent } from "../components/belt"; @@ -61,8 +62,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter { recomputeCacheFull() { logger.log("Full cache recompute in post load hook"); for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { this.recomputeSingleEntityCache(entity); @@ -149,9 +150,10 @@ export class ItemEjectorSystem extends GameSystemWithFilter { } // Go over all cache entries + dirInterval("ejectorItems", 30, this.allEntitiesArray); for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, sourceEntity; - (sourceEntity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, sourceEntity; + (sourceEntity = this.allEntitiesArray[i]) && i >= 0; --i ) { const sourceEjectorComp = sourceEntity.components.ItemEjector; diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 0978b73c..01510c7a 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -69,8 +69,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { update() { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const processorComp = entity.components.ItemProcessor; diff --git a/src/js/game/systems/lever.js b/src/js/game/systems/lever.js index 4710ed09..21f467ba 100644 --- a/src/js/game/systems/lever.js +++ b/src/js/game/systems/lever.js @@ -15,8 +15,8 @@ export class LeverSystem extends GameSystemWithFilter { update() { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const leverComp = entity.components.Lever; diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js index f9768461..6056dc7d 100644 --- a/src/js/game/systems/logic_gate.js +++ b/src/js/game/systems/logic_gate.js @@ -3,8 +3,9 @@ import { enumColors } from "../colors"; import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate"; import { enumPinSlotType } from "../components/wired_pins"; import { GameSystemWithFilter } from "../game_system_with_filter"; -import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item"; -import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; +import { BooleanItem, BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item"; +import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; +import { ShapeItem } from "../items/shape_item"; import { ShapeDefinition } from "../shape_definition"; export class LogicGateSystem extends GameSystemWithFilter { @@ -30,8 +31,8 @@ export class LogicGateSystem extends GameSystemWithFilter { update() { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const logicComp = entity.components.LogicGate; diff --git a/src/js/game/systems/miner.js b/src/js/game/systems/miner.js index 5aa05904..a86da376 100644 --- a/src/js/game/systems/miner.js +++ b/src/js/game/systems/miner.js @@ -37,8 +37,8 @@ export class MinerSystem extends GameSystemWithFilter { } for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const minerComp = entity.components.Miner; diff --git a/src/js/game/systems/storage.js b/src/js/game/systems/storage.js index 6eda7b1f..836c7bde 100644 --- a/src/js/game/systems/storage.js +++ b/src/js/game/systems/storage.js @@ -27,8 +27,8 @@ export class StorageSystem extends GameSystemWithFilter { update() { for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const storageComp = entity.components.Storage; diff --git a/src/js/game/systems/underground_belt.js b/src/js/game/systems/underground_belt.js index 139f7d7f..ce238abd 100644 --- a/src/js/game/systems/underground_belt.js +++ b/src/js/game/systems/underground_belt.js @@ -224,8 +224,8 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { update() { this.staleAreaWatcher.update(); for ( - let arr = this.getUpdateEntitiesArray(), i = arr.length - 1, entity; - (entity = arr[i]) && i >= 0; + let i = this.allEntitiesArray.length - 1, entity; + (entity = this.allEntitiesArray[i]) && i >= 0; --i ) { const undergroundComp = entity.components.UndergroundBelt; From be7d3c2c4f2c47d13ee479595a3659334a403909 Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 12:31:52 -0400 Subject: [PATCH 04/10] removing a debug call --- src/js/game/systems/item_ejector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index c4d668ee..ce4bcdd9 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -150,7 +150,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter { } // Go over all cache entries - dirInterval("ejectorItems", 30, this.allEntitiesArray); for ( let i = this.allEntitiesArray.length - 1, sourceEntity; (sourceEntity = this.allEntitiesArray[i]) && i >= 0; From 922a6680e58851d5d46d06568dc3e16af21ab785 Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 12:52:04 -0400 Subject: [PATCH 05/10] assignments in loops can be slower --- src/js/game/belt_path.js | 7 ++++--- src/js/game/game_system_with_filter.js | 12 ++++++++---- src/js/game/systems/belt_reader.js | 7 ++----- src/js/game/systems/constant_signal.js | 7 ++----- src/js/game/systems/filter.js | 7 ++----- src/js/game/systems/hub.js | 14 ++++---------- src/js/game/systems/item_acceptor.js | 7 ++----- src/js/game/systems/item_ejector.js | 14 ++++---------- src/js/game/systems/item_processor.js | 7 ++----- src/js/game/systems/lever.js | 7 ++----- src/js/game/systems/logic_gate.js | 7 ++----- src/js/game/systems/miner.js | 7 ++----- src/js/game/systems/storage.js | 7 ++----- src/js/game/systems/underground_belt.js | 7 ++----- 14 files changed, 40 insertions(+), 77 deletions(-) diff --git a/src/js/game/belt_path.js b/src/js/game/belt_path.js index b162f09f..a2b5af63 100644 --- a/src/js/game/belt_path.js +++ b/src/js/game/belt_path.js @@ -565,9 +565,10 @@ export class BeltPath extends BasicSerializableObject { beltComp.assignedPath = null; const entityLength = beltComp.getEffectiveLengthTiles(); - assert(this.entityPath.indexOf(entity) >= 0, "Entity not contained for split"); - assert(this.entityPath.indexOf(entity) !== 0, "Entity is first"); - assert(this.entityPath.indexOf(entity) !== this.entityPath.length - 1, "Entity is last"); + const index = this.entityPath.indexOf(entity); + assert(index >= 0, "Entity not contained for split"); + assert(index !== 0, "Entity is first"); + assert(index !== this.entityPath.length - 1, "Entity is last"); let firstPathEntityCount = 0; let firstPathLength = 0; diff --git a/src/js/game/game_system_with_filter.js b/src/js/game/game_system_with_filter.js index 013502ab..23126166 100644 --- a/src/js/game/game_system_with_filter.js +++ b/src/js/game/game_system_with_filter.js @@ -37,7 +37,10 @@ export class GameSystemWithFilter extends GameSystem { } tryUpdateEntitiesArray() { - this.allEntitiesArray = [...this.allEntitiesSet.values()]; + if (this.allEntitiesArrayIsOutdated) { + this.allEntitiesArray = [...this.allEntitiesSet.values()]; + this.allEntitiesArrayIsOutdated = false; + } } /** @@ -93,15 +96,16 @@ export class GameSystemWithFilter extends GameSystem { //this.allEntities.sort((a, b) => a.uid - b.uid); // Remove all entities which are queued for destroy - this.tryUpdateEntitiesArray(); - - for (let i = 0; i < this.allEntitiesArray.length; ++i) { + 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); } } + + // called here in case a delete executed mid frame + this.tryUpdateEntitiesArray(); } /** diff --git a/src/js/game/systems/belt_reader.js b/src/js/game/systems/belt_reader.js index 7d93e5cc..4f1b98dd 100644 --- a/src/js/game/systems/belt_reader.js +++ b/src/js/game/systems/belt_reader.js @@ -13,11 +13,8 @@ export class BeltReaderSystem extends GameSystemWithFilter { const minimumTime = now - globalConfig.readerAnalyzeIntervalSeconds; const minimumTimeForThroughput = now - 1; - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const readerComp = entity.components.BeltReader; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index 4803347e..5a6542f9 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -19,11 +19,8 @@ export class ConstantSignalSystem extends GameSystemWithFilter { update() { // Set signals - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const pinsComp = entity.components.WiredPins; const signalComp = entity.components.ConstantSignal; pinsComp.slots[0].value = signalComp.signal; diff --git a/src/js/game/systems/filter.js b/src/js/game/systems/filter.js index b9cd3f2d..53675858 100644 --- a/src/js/game/systems/filter.js +++ b/src/js/game/systems/filter.js @@ -20,11 +20,8 @@ export class FilterSystem extends GameSystemWithFilter { const requiredProgress = 1 - progress; - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const filterComp = entity.components.Filter; const ejectorComp = entity.components.ItemEjector; diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index 56442e39..556a9412 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -25,21 +25,15 @@ export class HubSystem extends GameSystemWithFilter { * @param {DrawParameters} parameters */ draw(parameters) { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; this.drawEntity(parameters, entity); } } update() { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const pinsComp = entity.components.WiredPins; pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition( this.root.hubGoals.currentGoal.definition diff --git a/src/js/game/systems/item_acceptor.js b/src/js/game/systems/item_acceptor.js index 2a071b5e..8d0977d4 100644 --- a/src/js/game/systems/item_acceptor.js +++ b/src/js/game/systems/item_acceptor.js @@ -39,11 +39,8 @@ export class ItemAcceptorSystem extends GameSystemWithFilter { // Reset accumulated ticks this.accumulatedTicksWhileInMapOverview = 0; - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const aceptorComp = entity.components.ItemAcceptor; const animations = aceptorComp.itemConsumptionAnimations; diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index ce4bcdd9..1b33e86f 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -61,11 +61,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter { */ recomputeCacheFull() { logger.log("Full cache recompute in post load hook"); - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; this.recomputeSingleEntityCache(entity); } } @@ -150,11 +147,8 @@ export class ItemEjectorSystem extends GameSystemWithFilter { } // Go over all cache entries - for ( - let i = this.allEntitiesArray.length - 1, sourceEntity; - (sourceEntity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const sourceEntity = this.allEntitiesArray[i]; const sourceEjectorComp = sourceEntity.components.ItemEjector; const slots = sourceEjectorComp.slots; diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 01510c7a..20ea5d04 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -68,11 +68,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } update() { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const processorComp = entity.components.ItemProcessor; const ejectorComp = entity.components.ItemEjector; diff --git a/src/js/game/systems/lever.js b/src/js/game/systems/lever.js index 21f467ba..0997f6a1 100644 --- a/src/js/game/systems/lever.js +++ b/src/js/game/systems/lever.js @@ -14,11 +14,8 @@ export class LeverSystem extends GameSystemWithFilter { } update() { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const leverComp = entity.components.Lever; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js index 6056dc7d..ef42c649 100644 --- a/src/js/game/systems/logic_gate.js +++ b/src/js/game/systems/logic_gate.js @@ -30,11 +30,8 @@ export class LogicGateSystem extends GameSystemWithFilter { } update() { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const logicComp = entity.components.LogicGate; const slotComp = entity.components.WiredPins; diff --git a/src/js/game/systems/miner.js b/src/js/game/systems/miner.js index a86da376..1b4734df 100644 --- a/src/js/game/systems/miner.js +++ b/src/js/game/systems/miner.js @@ -36,11 +36,8 @@ export class MinerSystem extends GameSystemWithFilter { miningSpeed *= 100; } - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const minerComp = entity.components.Miner; // Reset everything on recompute diff --git a/src/js/game/systems/storage.js b/src/js/game/systems/storage.js index 836c7bde..69eb5a3f 100644 --- a/src/js/game/systems/storage.js +++ b/src/js/game/systems/storage.js @@ -26,11 +26,8 @@ export class StorageSystem extends GameSystemWithFilter { } update() { - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const storageComp = entity.components.Storage; const pinsComp = entity.components.WiredPins; diff --git a/src/js/game/systems/underground_belt.js b/src/js/game/systems/underground_belt.js index ce238abd..8e736441 100644 --- a/src/js/game/systems/underground_belt.js +++ b/src/js/game/systems/underground_belt.js @@ -223,11 +223,8 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { update() { this.staleAreaWatcher.update(); - for ( - let i = this.allEntitiesArray.length - 1, entity; - (entity = this.allEntitiesArray[i]) && i >= 0; - --i - ) { + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; const undergroundComp = entity.components.UndergroundBelt; if (undergroundComp.mode === enumUndergroundBeltMode.sender) { this.handleSender(entity); From ea23dfa99660079386047b764a9a6137cd78b32d Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 13:13:53 -0400 Subject: [PATCH 06/10] fixed an iteration in belt --- src/js/game/systems/belt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/game/systems/belt.js b/src/js/game/systems/belt.js index 10543e6c..181a7fe2 100644 --- a/src/js/game/systems/belt.js +++ b/src/js/game/systems/belt.js @@ -422,8 +422,8 @@ export class BeltSystem extends GameSystemWithFilter { const result = []; - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; + for (let i = this.allEntitiesArray.length - 1; i >= 0; --i) { + const entity = this.allEntitiesArray[i]; if (visitedUids.has(entity.uid)) { continue; } From 99b57297767f91ff83e2c8ce3d535078baf090bf Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 14:55:45 -0400 Subject: [PATCH 07/10] 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); From 5ee113f4dd96e163d706f70214d553a64fe42210 Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 15:00:11 -0400 Subject: [PATCH 08/10] forgot a deletion --- src/js/game/entity_manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/game/entity_manager.js b/src/js/game/entity_manager.js index 580b2d5d..c479093e 100644 --- a/src/js/game/entity_manager.js +++ b/src/js/game/entity_manager.js @@ -227,6 +227,7 @@ export class EntityManager extends BasicSerializableObject { // Remove from entities list this.entities.delete(entity); + this.entitiesByUid.delete(entity.uid); // Remove from componentToEntity list this.unregisterEntityComponents(entity); From 6a19a944355140cc4409869d97456c274f303f22 Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 20:32:39 -0400 Subject: [PATCH 09/10] fixing serializer changes --- src/js/core/config.local.js | 6 +++--- src/js/game/core.js | 2 +- src/js/savegame/schemas/1006.js | 15 ++++++++++++--- src/js/savegame/serializer_internal.js | 9 ++++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/js/core/config.local.js b/src/js/core/config.local.js index 87aaaa14..b5aa3572 100644 --- a/src/js/core/config.local.js +++ b/src/js/core/config.local.js @@ -9,7 +9,7 @@ export default { // noArtificialDelays: true, // ----------------------------------------------------------------------------------- // Disables writing of savegames, useful for testing the same savegame over and over - // disableSavegameWrite: true, + disableSavegameWrite: true, // ----------------------------------------------------------------------------------- // Shows bounds of all entities // showEntityBounds: true, @@ -33,7 +33,7 @@ export default { // allBuildingsUnlocked: true, // ----------------------------------------------------------------------------------- // Disables cost of blueprints - // blueprintsNoCost: true, + blueprintsNoCost: true, // ----------------------------------------------------------------------------------- // Disables cost of upgrades // upgradesNoCost: true, @@ -75,7 +75,7 @@ export default { // instantMiners: true, // ----------------------------------------------------------------------------------- // When using fastGameEnter, controls whether a new game is started or the last one is resumed - // resumeGameOnFastEnter: true, + resumeGameOnFastEnter: true, // ----------------------------------------------------------------------------------- // Special option used to render the trailer // renderForTrailer: true, diff --git a/src/js/game/core.js b/src/js/game/core.js index 306643f9..73580a0e 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -417,7 +417,7 @@ export class GameCore { this.overlayAlpha = lerp(this.overlayAlpha, desiredOverlayAlpha, 0.25); // On low performance, skip the fade - if (this.root.entityMgr.entities.length > 5000 || this.root.dynamicTickrate.averageFps < 50) { + if (this.root.entityMgr.entities.size > 5000 || this.root.dynamicTickrate.averageFps < 50) { this.overlayAlpha = desiredOverlayAlpha; } diff --git a/src/js/savegame/schemas/1006.js b/src/js/savegame/schemas/1006.js index e6b2e263..f4512bcf 100644 --- a/src/js/savegame/schemas/1006.js +++ b/src/js/savegame/schemas/1006.js @@ -253,8 +253,15 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 { newStaticComp.originalRotation = staticComp.originalRotation; newStaticComp.rotation = staticComp.rotation; - // @ts-ignore - newStaticComp.code = spriteMapping[staticComp.blueprintSpriteKey]; + /** + * in one of our files: + * we dont seem to actually have a blueprintspritekey + * but we do have this attribute called code + */ + + if (staticComp.blueprintSpriteKey) { + newStaticComp.code = spriteMapping[staticComp.blueprintSpriteKey]; + } else newStaticComp.code = staticComp.code; // Hub special case if (entity.components.Hub) { @@ -277,9 +284,11 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 { } if (!newStaticComp.code) { + console.dir(entity); + console.dir(staticComp); throw new Error( // @ts-ignore - "1006 Migration: Could not reconstruct code for " + staticComp.blueprintSpriteKey + "1006 Migration: Could not reconstruct code for " + code ); } diff --git a/src/js/savegame/serializer_internal.js b/src/js/savegame/serializer_internal.js index fa02a437..940283d0 100644 --- a/src/js/savegame/serializer_internal.js +++ b/src/js/savegame/serializer_internal.js @@ -11,12 +11,15 @@ const logger = createLogger("serializer_internal"); export class SerializerInternal { /** * Serializes an array of entities - * @param {Array} array + * @param {Array|Set} array */ serializeEntityArray(array) { const serialized = []; - for (let i = 0; i < array.length; ++i) { - const entity = array[i]; + + const arr = Array.isArray(array) ? array : [...array.values()]; + + for (let i = 0; i < arr.length; ++i) { + const entity = arr[i]; if (!entity.queuedForDestroy && !entity.destroyed) { serialized.push(entity.serialize()); } From f160fc14aaccb8a67f1c94dd072b02e9de936d9a Mon Sep 17 00:00:00 2001 From: dgs4349 Date: Sun, 27 Sep 2020 21:33:28 -0400 Subject: [PATCH 10/10] made blueprints and selections faster --- src/js/game/blueprint.js | 31 ++++++ src/js/game/entity_manager.js | 2 +- src/js/game/hud/hud.js | 3 +- src/js/game/hud/parts/blueprint_placer.js | 9 +- src/js/game/hud/parts/mass_selector.js | 122 ++++++++++++---------- 5 files changed, 108 insertions(+), 59 deletions(-) diff --git a/src/js/game/blueprint.js b/src/js/game/blueprint.js index a37ea20d..9585f973 100644 --- a/src/js/game/blueprint.js +++ b/src/js/game/blueprint.js @@ -62,6 +62,37 @@ export class Blueprint { return new Blueprint(newEntities); } + /** + * Creates a new blueprint from the given entity uids + * @param {Array} entities + */ + static fromEntities(entities) { + const newEntities = []; + + let averagePosition = new Vector(); + + // First, create a copy + for (let i = entities.length - 1; i >= 0; --i) { + const entity = entities[i]; + + const clone = entity.clone(); + newEntities.push(clone); + + const pos = entity.components.StaticMapEntity.getTileSpaceBounds().getCenter(); + averagePosition.addInplace(pos); + } + + averagePosition.divideScalarInplace(entities.length); + const blueprintOrigin = averagePosition.subScalars(0.5, 0.5).floor(); + + for (let i = newEntities.length - 1; i >= 0; --i) { + newEntities[i].components.StaticMapEntity.origin.subInplace(blueprintOrigin); + } + + // Now, make sure the origin is 0,0 + return new Blueprint(newEntities); + } + /** * Returns the cost of this blueprint in shapes */ diff --git a/src/js/game/entity_manager.js b/src/js/game/entity_manager.js index c479093e..bb86c151 100644 --- a/src/js/game/entity_manager.js +++ b/src/js/game/entity_manager.js @@ -95,7 +95,7 @@ export class EntityManager extends BasicSerializableObject { } // Give each entity a unique id - entity.uid = uid ? uid : this.generateUid(); + entity.uid = uid || this.generateUid(); this.entities.add(entity); this.entitiesByUid.set(uid, entity); diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index e0ddfd9d..978fc9f9 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -44,6 +44,7 @@ import { HUDWireInfo } from "./parts/wire_info"; import { HUDLeverToggle } from "./parts/lever_toggle"; import { HUDLayerPreview } from "./parts/layer_preview"; import { HUDMinerHighlight } from "./parts/miner_highlight"; +import { Entity } from "../entity"; export class GameHUD { /** @@ -99,7 +100,7 @@ export class GameHUD { shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()), notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()), - buildingsSelectedForCopy: /** @type {TypedSignal<[Array]>} */ (new Signal()), + buildingsSelectedForCopy: /** @type {TypedSignal<[Array]>} */ (new Signal()), pasteBlueprintRequested: /** @type {TypedSignal<[]>} */ (new Signal()), viewShapeDetailsRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), }; diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index 47bf1363..d88fbf94 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -11,6 +11,7 @@ import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { Blueprint } from "../../blueprint"; import { SOUNDS } from "../../../platform/sound"; +import { Entity } from "../../entity"; export class HUDBlueprintPlacer extends BaseHUDPart { createElements(parent) { @@ -140,13 +141,13 @@ export class HUDBlueprintPlacer extends BaseHUDPart { /** * Called when an array of bulidings was selected - * @param {Array} uids + * @param {Array} entities */ - createBlueprintFromBuildings(uids) { - if (uids.length === 0) { + createBlueprintFromBuildings(entities) { + if (entities.length === 0) { return; } - this.currentBlueprint.set(Blueprint.fromUids(this.root, uids)); + this.currentBlueprint.set(Blueprint.fromEntities(entities)); } /** diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 08a11769..87ce453c 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -5,7 +5,7 @@ import { DrawParameters } from "../../../core/draw_parameters"; import { Entity } from "../../entity"; import { Loader } from "../../../core/loader"; import { globalConfig } from "../../../core/config"; -import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils"; +import { makeDiv, formatBigNumber, formatBigNumberFull, dirInterval } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { createLogger } from "../../../core/logging"; import { enumMouseButton } from "../../camera"; @@ -23,7 +23,12 @@ export class HUDMassSelector extends BaseHUDPart { initialize() { this.currentSelectionStartWorld = null; this.currentSelectionEnd = null; - this.selectedUids = new Set(); + + /** @type {Set} */ + this.selectedEntities = new Set(); + + /** @type {number} */ + this.selectedUids = 42; this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this); this.root.hud.signals.pasteBlueprintRequested.add(this.clearSelection, this); @@ -43,6 +48,20 @@ export class HUDMassSelector extends BaseHUDPart { this.root.signals.editModeChanged.add(this.clearSelection, this); } + clear() { + this.selectedEntities.clear(); + } + + // getUidArray() { + // if (this.selectedEntities.size <= 0) return []; + // const uids = []; + // const arr = [...this.selectedEntities.values()]; + // for (let i = arr.length - 1; i >= 0; --i) { + // uids.push(arr[i].uid); + // } + // return uids; + // } + /** * Handles the destroy callback and makes sure we clean our list * @param {Entity} entity @@ -51,7 +70,7 @@ export class HUDMassSelector extends BaseHUDPart { if (this.root.bulkOperationRunning) { return; } - this.selectedUids.delete(entity.uid); + this.selectedEntities.delete(entity); } /** @@ -59,8 +78,8 @@ export class HUDMassSelector extends BaseHUDPart { */ onBack() { // Clear entities on escape - if (this.selectedUids.size > 0) { - this.selectedUids = new Set(); + if (this.selectedEntities.size > 0) { + this.clear(); return STOP_PROPAGATION; } } @@ -69,19 +88,19 @@ export class HUDMassSelector extends BaseHUDPart { * Clears the entire selection */ clearSelection() { - this.selectedUids = new Set(); + this.clear(); } confirmDelete() { if ( !this.root.app.settings.getAllSettings().disableCutDeleteWarnings && - this.selectedUids.size > 100 + this.selectedEntities.size > 100 ) { const { ok } = this.root.hud.parts.dialogs.showWarning( T.dialogs.massDeleteConfirm.title, T.dialogs.massDeleteConfirm.desc.replace( "", - "" + formatBigNumberFull(this.selectedUids.size) + "" + formatBigNumberFull(this.selectedEntities.size) ), ["cancel:good:escape", "ok:bad:enter"] ); @@ -92,35 +111,26 @@ export class HUDMassSelector extends BaseHUDPart { } doDelete() { - const entityUids = Array.from(this.selectedUids); - // Build mapping from uid to entity /** * @type {Map} */ - const mapUidToEntity = this.root.entityMgr.getFrozenUidSearchMap(); + //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)) { + const arr = [...this.selectedEntities.values()]; + for (let i = arr.length - 1; i >= 0; --i) { + if (!this.root.logic.tryDeleteBuilding(arr[i])) { logger.error("Error in mass delete, could not remove building"); } } }); - // Clear uids later - this.selectedUids = new Set(); + this.clear(); } startCopy() { - if (this.selectedUids.size > 0) { + if (this.selectedEntities.size > 0) { if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { this.root.hud.parts.dialogs.showInfo( T.dialogs.blueprintsNotUnlocked.title, @@ -128,8 +138,10 @@ export class HUDMassSelector extends BaseHUDPart { ); return; } - this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids)); - this.selectedUids = new Set(); + const uids = []; + + this.root.hud.signals.buildingsSelectedForCopy.dispatch([...this.selectedEntities.values()]); + this.selectedEntities.clear(); this.root.soundProxy.playUiClick(); } else { this.root.soundProxy.playUiError(); @@ -144,13 +156,13 @@ export class HUDMassSelector extends BaseHUDPart { ); } else if ( !this.root.app.settings.getAllSettings().disableCutDeleteWarnings && - this.selectedUids.size > 100 + this.selectedEntities.size > 100 ) { const { ok } = this.root.hud.parts.dialogs.showWarning( T.dialogs.massCutConfirm.title, T.dialogs.massCutConfirm.desc.replace( "", - "" + formatBigNumberFull(this.selectedUids.size) + "" + formatBigNumberFull(this.selectedEntities.size) ), ["cancel:good:escape", "ok:bad:enter"] ); @@ -161,26 +173,26 @@ export class HUDMassSelector extends BaseHUDPart { } doCut() { - if (this.selectedUids.size > 0) { - const entityUids = Array.from(this.selectedUids); - - const cutAction = () => { + if (this.selectedEntities.size > 0) { + const cutAction = argArray => { + const arr = argArray || [...this.selectedEntities.values()]; // copy code relies on entities still existing, so must copy before deleting. - this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids); - for (let i = 0; i < entityUids.length; ++i) { - const uid = entityUids[i]; - const entity = this.root.entityMgr.findByUid(uid); + this.root.hud.signals.buildingsSelectedForCopy.dispatch(arr); + + for (let i = arr.length - 1; i >= 0; --i) { + const entity = arr[i]; if (!this.root.logic.tryDeleteBuilding(entity)) { logger.error("Error in mass cut, could not remove building"); - this.selectedUids.delete(uid); + this.selectedEntities.delete(entity); } } }; - const blueprint = Blueprint.fromUids(this.root, entityUids); + const arr = [...this.selectedEntities.values()]; + const blueprint = Blueprint.fromEntities(arr); if (blueprint.canAfford(this.root)) { - cutAction(); + cutAction(arr); } else { const { cancel, ok } = this.root.hud.parts.dialogs.showWarning( T.dialogs.massCutInsufficientConfirm.title, @@ -212,7 +224,7 @@ export class HUDMassSelector extends BaseHUDPart { if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).pressed) { // Start new selection - this.selectedUids = new Set(); + this.clear(); } this.currentSelectionStartWorld = this.root.camera.screenToWorld(pos.copy()); @@ -245,7 +257,7 @@ export class HUDMassSelector extends BaseHUDPart { for (let y = realTileStart.y; y <= realTileEnd.y; ++y) { const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer); if (contents && this.root.logic.canDeleteBuilding(contents)) { - this.selectedUids.add(contents.uid); + this.selectedEntities.add(contents); } } } @@ -319,18 +331,22 @@ export class HUDMassSelector extends BaseHUDPart { } parameters.context.fillStyle = THEME.map.selectionOverlay; - this.selectedUids.forEach(uid => { - const entity = this.root.entityMgr.findByUid(uid); - const staticComp = entity.components.StaticMapEntity; - const bounds = staticComp.getTileSpaceBounds(); - parameters.context.beginRoundedRect( - bounds.x * globalConfig.tileSize + boundsBorder, - bounds.y * globalConfig.tileSize + boundsBorder, - bounds.w * globalConfig.tileSize - 2 * boundsBorder, - bounds.h * globalConfig.tileSize - 2 * boundsBorder, - 2 - ); - parameters.context.fill(); - }); + + if (this.selectedEntities.size > 0) { + const arr = [...this.selectedEntities.values()]; + for (let i = arr.length - 1; i >= 0; --i) { + const entity = arr[i]; + const staticComp = entity.components.StaticMapEntity; + const bounds = staticComp.getTileSpaceBounds(); + parameters.context.beginRoundedRect( + bounds.x * globalConfig.tileSize + boundsBorder, + bounds.y * globalConfig.tileSize + boundsBorder, + bounds.w * globalConfig.tileSize - 2 * boundsBorder, + bounds.h * globalConfig.tileSize - 2 * boundsBorder, + 2 + ); + parameters.context.fill(); + } + } } }