Skip to content

Commit 040766d

Browse files
authored
Add units filter on playeractions for performance (#3213)
## Description: The ghost structure calls player actions each frame, which is costly since it's checking for all possible actions. This add a unit list filter in actions so if there are units it only checks for buildability of those units. Before: ![WhatsApp Image 2026-02-14 at 23 25 25](https://github.com/user-attachments/assets/beda6142-9dc7-4a9c-a702-cee3b6ea043c) Player actions takes 20-30% of the worker After: <img width="825" height="342" alt="image" src="https://github.com/user-attachments/assets/36e47547-5028-4dc9-bc42-e17df4a87200" /> Player actions takes 1-3% of the worker Both performances are relevant only when a ghost structure is selected ## 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: Mr. Box
1 parent 1e5db18 commit 040766d

File tree

10 files changed

+66
-45
lines changed

10 files changed

+66
-45
lines changed

src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class NukeTrajectoryPreviewLayer implements Layer {
138138

139139
// Get buildable units to find spawn tile (expensive call - only on tick when tile changes)
140140
player
141-
.actions(targetTile)
141+
.actions(targetTile, [ghostStructure])
142142
.then((actions) => {
143143
// Ignore stale results if target changed
144144
if (this.lastTargetTile !== targetTile) {

src/client/graphics/layers/StructureIconsLayer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export class StructureIconsLayer implements Layer {
285285

286286
this.game
287287
?.myPlayer()
288-
?.actions(tileRef)
288+
?.actions(tileRef, [this.ghostUnit?.buildableUnit.type])
289289
.then((actions) => {
290290
if (this.potentialUpgrade) {
291291
this.potentialUpgrade.iconContainer.filters = [];

src/client/graphics/layers/UnitDisplay.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ import portIcon from "/images/PortIcon.svg?url";
2222
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
2323
import defensePostIcon from "/images/ShieldIconWhite.svg?url";
2424

25+
const BUILDABLE_UNITS: UnitType[] = [
26+
UnitType.City,
27+
UnitType.Factory,
28+
UnitType.Port,
29+
UnitType.DefensePost,
30+
UnitType.MissileSilo,
31+
UnitType.SAMLauncher,
32+
UnitType.Warship,
33+
UnitType.AtomBomb,
34+
UnitType.HydrogenBomb,
35+
UnitType.MIRV,
36+
];
37+
2538
@customElement("unit-display")
2639
export class UnitDisplay extends LitElement implements Layer {
2740
public game: GameView;
@@ -55,17 +68,7 @@ export class UnitDisplay extends LitElement implements Layer {
5568
}
5669
}
5770

58-
this.allDisabled =
59-
config.isUnitDisabled(UnitType.City) &&
60-
config.isUnitDisabled(UnitType.Factory) &&
61-
config.isUnitDisabled(UnitType.Port) &&
62-
config.isUnitDisabled(UnitType.DefensePost) &&
63-
config.isUnitDisabled(UnitType.MissileSilo) &&
64-
config.isUnitDisabled(UnitType.SAMLauncher) &&
65-
config.isUnitDisabled(UnitType.Warship) &&
66-
config.isUnitDisabled(UnitType.AtomBomb) &&
67-
config.isUnitDisabled(UnitType.HydrogenBomb) &&
68-
config.isUnitDisabled(UnitType.MIRV);
71+
this.allDisabled = BUILDABLE_UNITS.every((u) => config.isUnitDisabled(u));
6972
this.requestUpdate();
7073
}
7174

@@ -101,7 +104,7 @@ export class UnitDisplay extends LitElement implements Layer {
101104

102105
tick() {
103106
const player = this.game?.myPlayer();
104-
player?.actions().then((actions) => {
107+
player?.actions(undefined, BUILDABLE_UNITS).then((actions) => {
105108
this.playerActions = actions;
106109
});
107110
if (!player) return;

src/core/GameRunner.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,14 @@ export class GameRunner {
195195
playerID: PlayerID,
196196
x?: number,
197197
y?: number,
198+
units?: UnitType[],
198199
): PlayerActions {
199200
const player = this.game.player(playerID);
200201
const tile =
201202
x !== undefined && y !== undefined ? this.game.ref(x, y) : null;
202203
const actions = {
203-
canAttack: tile !== null && player.canAttack(tile),
204-
buildableUnits: player.buildableUnits(tile),
204+
canAttack: tile !== null && units === undefined && player.canAttack(tile),
205+
buildableUnits: player.buildableUnits(tile, units),
205206
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
206207
canEmbargoAll: player.canEmbargoAll(),
207208
} as PlayerActions;

src/core/game/Game.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ export interface Player {
627627
unitCount(type: UnitType): number;
628628
unitsConstructed(type: UnitType): number;
629629
unitsOwned(type: UnitType): number;
630-
buildableUnits(tile: TileRef | null): BuildableUnit[];
630+
buildableUnits(tile: TileRef | null, units?: UnitType[]): BuildableUnit[];
631631
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
632632
buildUnit<T extends UnitType>(
633633
type: T,

src/core/game/GameView.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,12 @@ export class PlayerView {
403403
return { hasEmbargo, hasFriendly };
404404
}
405405

406-
async actions(tile?: TileRef): Promise<PlayerActions> {
406+
async actions(tile?: TileRef, units?: UnitType[]): Promise<PlayerActions> {
407407
return this.game.worker.playerInteraction(
408408
this.id(),
409409
tile && this.game.x(tile),
410410
tile && this.game.y(tile),
411+
units,
411412
);
412413
}
413414

src/core/game/PlayerImpl.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
EmojiMessage,
2323
GameMode,
2424
Gold,
25+
isStructureType,
2526
MessageType,
2627
MutableAlliance,
2728
Player,
@@ -960,31 +961,40 @@ export class PlayerImpl implements Player {
960961
this.recordUnitConstructed(unit.type());
961962
}
962963

963-
public buildableUnits(tile: TileRef | null): BuildableUnit[] {
964-
const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : [];
965-
return Object.values(UnitType).map((u) => {
966-
let canUpgrade: number | false = false;
967-
let canBuild: TileRef | false = false;
968-
if (!this.mg.inSpawnPhase()) {
969-
const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile);
970-
if (existingUnit !== false) {
971-
canUpgrade = existingUnit.id();
972-
}
973-
if (tile !== null) {
974-
canBuild = this.canBuild(u, tile, validTiles);
964+
public buildableUnits(
965+
tile: TileRef | null,
966+
units?: UnitType[],
967+
): BuildableUnit[] {
968+
const validTiles =
969+
tile !== null &&
970+
(units === undefined || units.some((u) => isStructureType(u)))
971+
? this.validStructureSpawnTiles(tile)
972+
: [];
973+
return Object.values(UnitType)
974+
.filter((u) => units === undefined || units.includes(u))
975+
.map((u) => {
976+
let canUpgrade: number | false = false;
977+
let canBuild: TileRef | false = false;
978+
if (!this.mg.inSpawnPhase()) {
979+
const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile);
980+
if (existingUnit !== false) {
981+
canUpgrade = existingUnit.id();
982+
}
983+
if (tile !== null) {
984+
canBuild = this.canBuild(u, tile, validTiles);
985+
}
975986
}
976-
}
977-
return {
978-
type: u,
979-
canBuild,
980-
canUpgrade,
981-
cost: this.mg.config().unitInfo(u).cost(this.mg, this),
982-
overlappingRailroads:
983-
canBuild !== false
984-
? this.mg.railNetwork().overlappingRailroads(canBuild)
985-
: [],
986-
} as BuildableUnit;
987-
});
987+
return {
988+
type: u,
989+
canBuild,
990+
canUpgrade,
991+
cost: this.mg.config().unitInfo(u).cost(this.mg, this),
992+
overlappingRailroads:
993+
canBuild !== false
994+
? this.mg.railNetwork().overlappingRailroads(canBuild)
995+
: [],
996+
} as BuildableUnit;
997+
});
988998
}
989999

9901000
canBuild(

src/core/worker/Worker.worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
9494
message.playerID,
9595
message.x,
9696
message.y,
97+
message.units,
9798
);
9899
sendMessage({
99100
type: "player_actions_result",

src/core/worker/WorkerClient.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
PlayerBorderTiles,
55
PlayerID,
66
PlayerProfile,
7+
UnitType,
78
} from "../game/Game";
89
import { TileRef } from "../game/GameMap";
910
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
@@ -164,6 +165,7 @@ export class WorkerClient {
164165
playerID: PlayerID,
165166
x?: number,
166167
y?: number,
168+
units?: UnitType[],
167169
): Promise<PlayerActions> {
168170
return new Promise((resolve, reject) => {
169171
if (!this.isInitialized) {
@@ -185,9 +187,10 @@ export class WorkerClient {
185187
this.worker.postMessage({
186188
type: "player_actions",
187189
id: messageId,
188-
playerID: playerID,
189-
x: x,
190-
y: y,
190+
playerID,
191+
x,
192+
y,
193+
units,
191194
});
192195
});
193196
}

src/core/worker/WorkerMessages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
PlayerBorderTiles,
44
PlayerID,
55
PlayerProfile,
6+
UnitType,
67
} from "../game/Game";
78
import { TileRef } from "../game/GameMap";
89
import { GameUpdateViewData } from "../game/GameUpdates";
@@ -62,6 +63,7 @@ export interface PlayerActionsMessage extends BaseWorkerMessage {
6263
playerID: PlayerID;
6364
x?: number;
6465
y?: number;
66+
units?: UnitType[];
6567
}
6668

6769
export interface PlayerActionsResultMessage extends BaseWorkerMessage {

0 commit comments

Comments
 (0)