mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:50:42 +00:00
perf(UnitLayer): batch trail clears to fix O(n²) cost on mass nuke explosions (#3808)
## Description: When multiple nukes detonated in the same tick, clearTrail was called once per dying unit. Each call scanned all remaining units to repaint overlapping trail tiles — O(dead × alive × trail_len) per tick. Replace with a deferred batch: dying units are queued into pendingTrailClears during drawUnitsCells, then flushTrailClears() processes them all at once after the draw pass. All trail tiles are cleared in a single loop (skipping duplicates), followed by one repaint scan of surviving units — O((dead + alive) × trail_len). Also fixes a minor bug in the original: the surviving unit's relationship is now used when repainting its trail (previously the dying unit's relationship was used, which gave wrong colors in alternate-view mode). ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -40,6 +40,7 @@ export class UnitLayer implements Layer {
|
||||
private unitTrailContext: CanvasRenderingContext2D;
|
||||
|
||||
private unitToTrail = new Map<UnitView, TileRef[]>();
|
||||
private pendingTrailClears: UnitView[] = [];
|
||||
|
||||
private theme: Theme;
|
||||
|
||||
@@ -381,6 +382,7 @@ export class UnitLayer implements Layer {
|
||||
// otherwise the sprite of a unit can be drawn on top of another unit
|
||||
this.clearUnitsCells(unitsToUpdate);
|
||||
this.drawUnitsCells(unitsToUpdate);
|
||||
this.flushTrailClears();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,19 +548,33 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private clearTrail(unit: UnitView) {
|
||||
const trail = this.unitToTrail.get(unit) ?? [];
|
||||
const rel = this.relationship(unit);
|
||||
for (const t of trail) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t), this.unitTrailContext);
|
||||
}
|
||||
this.unitToTrail.delete(unit);
|
||||
private flushTrailClears() {
|
||||
if (this.pendingTrailClears.length === 0) return;
|
||||
|
||||
// Repaint overlapping trails
|
||||
const trailSet = new Set(trail);
|
||||
const clearedTiles = new Set<TileRef>();
|
||||
for (const unit of this.pendingTrailClears) {
|
||||
const trail = this.unitToTrail.get(unit);
|
||||
if (trail) {
|
||||
for (const t of trail) {
|
||||
if (!clearedTiles.has(t)) {
|
||||
this.clearCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
this.unitTrailContext,
|
||||
);
|
||||
clearedTiles.add(t);
|
||||
}
|
||||
}
|
||||
this.unitToTrail.delete(unit);
|
||||
}
|
||||
}
|
||||
this.pendingTrailClears = [];
|
||||
|
||||
// Single repaint pass for all remaining units
|
||||
for (const [other, trail] of this.unitToTrail) {
|
||||
const rel = this.relationship(other);
|
||||
for (const t of trail) {
|
||||
if (trailSet.has(t)) {
|
||||
if (clearedTiles.has(t)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
@@ -609,7 +625,7 @@ export class UnitLayer implements Layer {
|
||||
);
|
||||
this.drawSprite(unit);
|
||||
if (!unit.isActive()) {
|
||||
this.clearTrail(unit);
|
||||
this.pendingTrailClears.push(unit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,7 +668,7 @@ export class UnitLayer implements Layer {
|
||||
this.drawSprite(unit);
|
||||
|
||||
if (!unit.isActive()) {
|
||||
this.clearTrail(unit);
|
||||
this.pendingTrailClears.push(unit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user