diff --git a/src/js/game/components/belt.js b/src/js/game/components/belt.js index d98db49a..a9be5c99 100644 --- a/src/js/game/components/belt.js +++ b/src/js/game/components/belt.js @@ -5,6 +5,7 @@ import { BaseItem } from "../base_item"; import { Vector, enumDirection } from "../../core/vector"; import { Math_PI, Math_sin, Math_cos } from "../../core/builtins"; import { globalConfig } from "../../core/config"; +import { Entity } from "../entity"; export class BeltComponent extends Component { static getId() { @@ -12,6 +13,7 @@ export class BeltComponent extends Component { } static getSchema() { + // The followUpCache field is not serialized. return { direction: types.string, sortedItems: types.array(types.pair(types.float, types.obj(gItemRegistry))), @@ -34,6 +36,9 @@ export class BeltComponent extends Component { /** @type {Array<[number, BaseItem]>} */ this.sortedItems = []; + + /** @type {Entity} */ + this.followUpCache = null; } /** diff --git a/src/js/game/systems/belt.js b/src/js/game/systems/belt.js index 5fa5a265..a4b6d85f 100644 --- a/src/js/game/systems/belt.js +++ b/src/js/game/systems/belt.js @@ -13,14 +13,13 @@ import { MetaBeltBaseBuilding } from "../buildings/belt_base"; import { defaultBuildingVariant } from "../meta_building"; import { GameRoot } from "../root"; import { createLogger } from "../../core/logging"; +import { Rectangle } from "../../core/rectangle"; const BELT_ANIM_COUNT = 6; const SQRT_2 = Math_sqrt(2); const logger = createLogger("belt"); -/** @typedef {Array<{ entity: Entity, followUp: Entity }>} BeltCache */ - export class BeltSystem extends GameSystemWithFilter { constructor(root) { super(root, [BeltComponent]); @@ -66,9 +65,8 @@ export class BeltSystem extends GameSystemWithFilter { this.root.signals.entityDestroyed.add(this.updateSurroundingBeltPlacement, this); this.cacheNeedsUpdate = true; - - /** @type {BeltCache} */ - this.beltCache = []; + /** @type {Rectangle[]} */ + this.smallUpdateAreas = []; } /** @@ -119,6 +117,12 @@ export class BeltSystem extends GameSystemWithFilter { } } } + + // Optimize for the common case of adding or removing buildings one at a time. + // Clicking and dragging can fire up to 4 create/destroy signals. + if (this.cacheNeedsUpdate) { + this.smallUpdateAreas.push(affectedArea.expandedInAllDirections(1)); + } } draw(parameters) { @@ -163,42 +167,28 @@ export class BeltSystem extends GameSystemWithFilter { return null; } - /** - * Adds a single entity to the cache - * @param {Entity} entity - * @param {BeltCache} cache - * @param {Set} visited - */ - computeSingleBeltCache(entity, cache, visited) { - // Check for double visit - if (visited.has(entity.uid)) { - return; - } - visited.add(entity.uid); - - const followUp = this.findFollowUpEntity(entity); - if (followUp) { - // Process followup first - this.computeSingleBeltCache(followUp, cache, visited); - } - - cache.push({ entity, followUp }); - } - computeBeltCache() { logger.log("Updating belt cache"); - let cache = []; - let visited = new Set(); - for (let i = 0; i < this.allEntities.length; ++i) { - this.computeSingleBeltCache(this.allEntities[i], cache, visited); + if (this.smallUpdateAreas.length <= 4) { + for (let i = 0; i < this.smallUpdateAreas.length; ++i) { + const area = this.smallUpdateAreas[i]; + for (let x = area.x; x < area.right(); ++x) { + for (let y = area.y; y < area.bottom(); ++y) { + const tile = this.root.map.getTileContentXY(x, y); + if (tile && tile.components.Belt) { + tile.components.Belt.followUpCache = this.findFollowUpEntity(tile); + } + } + } + } + } else { + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + entity.components.Belt.followUpCache = this.findFollowUpEntity(entity); + } } - assert( - cache.length === this.allEntities.length, - "Belt cache mismatch: Has " + cache.length + " entries but should have " + this.allEntities.length - ); - - this.beltCache = cache; + this.smallUpdateAreas = []; } update() { @@ -217,8 +207,8 @@ export class BeltSystem extends GameSystemWithFilter { beltSpeed *= 100; } - for (let i = 0; i < this.beltCache.length; ++i) { - const { entity, followUp } = this.beltCache[i]; + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; const beltComp = entity.components.Belt; const items = beltComp.sortedItems; @@ -244,8 +234,8 @@ export class BeltSystem extends GameSystemWithFilter { maxProgress = 1 - globalConfig.itemSpacingOnBelts; } else { // Otherwise our progress depends on the follow up - if (followUp) { - const spacingOnBelt = followUp.components.Belt.getDistanceToFirstItemCenter(); + if (beltComp.followUpCache) { + const spacingOnBelt = beltComp.followUpCache.components.Belt.getDistanceToFirstItemCenter(); maxProgress = Math.min(2, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt); // Useful check, but hurts performance @@ -270,8 +260,8 @@ export class BeltSystem extends GameSystemWithFilter { progressAndItem[0] = Math.min(maxProgress, progressAndItem[0] + speedMultiplier * beltSpeed); if (progressAndItem[0] >= 1.0) { - if (followUp) { - const followUpBelt = followUp.components.Belt; + if (beltComp.followUpCache) { + const followUpBelt = beltComp.followUpCache.components.Belt; if (followUpBelt.canAcceptItem()) { followUpBelt.takeItem(progressAndItem[1], progressAndItem[0] - 1.0); items.splice(itemIndex, 1);