perf(core): speed up packedTileUpdates (Uint32 pairs, no tile wrappers) (#3255)

## Description
Reduces CPU + GC pressure from tile update serialization.

**What changed**
- Switched `packedTileUpdates` from `BigUint64Array` (BigInt packing) to
`Uint32Array` `[tileRef, state]` pairs, updating `GameView` ingestion.
- Updated tile state to use `GameMap.tileState(tile)` and
`GameMap.updateTile(tile, state)`.
- Removed per-tile `GameUpdateType.Tile` wrapper allocations by
recording raw `(tile, state)` pairs in `GameImpl` and draining them via
`drainPackedTileUpdates()` in `GameRunner`.

**Why it’s faster**
- Avoids BigInt and pack/unpack.
- Avoids per-tile object allocations.

**Compatibility**
- Wire format change: `packedTileUpdates` is now `Uint32Array` pairs
instead of `BigUint64Array`.

## Please complete the following:

- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] 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:

DISCORD_USERNAME
This commit is contained in:
scamiv
2026-02-21 06:01:03 +01:00
committed by GitHub
parent 444aa16ac8
commit ea2a76609f
6 changed files with 68 additions and 63 deletions
+24 -17
View File
@@ -39,7 +39,7 @@ import {
UnitInfo,
UnitType,
} from "./Game";
import { GameMap, TileRef, TileUpdate } from "./GameMap";
import { GameMap, TileRef } from "./GameMap";
import { GameUpdate, GameUpdateType } from "./GameUpdates";
import { PlayerImpl } from "./PlayerImpl";
import { RailNetwork } from "./RailNetwork";
@@ -83,6 +83,7 @@ export class GameImpl implements Game {
private _nextUnitID = 1;
private updates: GameUpdates = createGameUpdatesMap();
private tileUpdatePairs: number[] = [];
private unitGrid: UnitGrid;
private playerTeams: Team[];
@@ -248,10 +249,7 @@ export class GameImpl implements Game {
return;
}
this._map.setFallout(tile, value);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.recordTileUpdate(tile);
}
units(...types: UnitType[]): Unit[] {
@@ -379,6 +377,7 @@ export class GameImpl implements Game {
executeNextTick(): GameUpdates {
this.updates = createGameUpdatesMap();
this.tileUpdatePairs.length = 0;
this.execs.forEach((e) => {
if (
(!this.inSpawnPhase() || e.activeDuringSpawnPhase()) &&
@@ -417,6 +416,20 @@ export class GameImpl implements Game {
return this.updates;
}
private recordTileUpdate(tile: TileRef): void {
this.tileUpdatePairs.push(tile, this._map.tileState(tile));
}
drainPackedTileUpdates(): Uint32Array {
const pairs = this.tileUpdatePairs;
const packed = new Uint32Array(pairs.length);
for (let i = 0; i < pairs.length; i++) {
packed[i] = pairs[i];
}
pairs.length = 0;
return packed;
}
private hash(): number {
let hash = 1;
this._players.forEach((p) => {
@@ -588,10 +601,7 @@ export class GameImpl implements Game {
owner._lastTileChange = this._ticks;
this.updateBorders(tile);
this._map.setFallout(tile, false);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.recordTileUpdate(tile);
}
relinquish(tile: TileRef) {
@@ -609,10 +619,7 @@ export class GameImpl implements Game {
this._map.setOwnerID(tile, 0);
this.updateBorders(tile);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.recordTileUpdate(tile);
}
private updateBorders(tile: TileRef) {
@@ -1017,11 +1024,11 @@ export class GameImpl implements Game {
): Set<TileRef> {
return this._map.bfs(tile, filter);
}
toTileUpdate(tile: TileRef): bigint {
return this._map.toTileUpdate(tile);
tileState(tile: TileRef): number {
return this._map.tileState(tile);
}
updateTile(tu: TileUpdate): TileRef {
return this._map.updateTile(tu);
updateTile(tile: TileRef, state: number): void {
this._map.updateTile(tile, state);
}
numTilesWithFallout(): number {
return this._map.numTilesWithFallout();