Perf alloc (#3241)

If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

## PR Title
perf(core): reduce hot-path allocations & safe optimizations

This PR brings in a set of allocation-focused optimizations in core hot
paths

### Scope
- `src/core/execution/NukeExecution.ts`
- `src/core/execution/WarshipExecution.ts`
- `src/core/game/UnitGrid.ts`
- `src/core/game/PlayerImpl.ts`
- `src/core/configuration/DefaultConfig.ts`
- `src/core/execution/SAMLauncherExecution.ts`

### What Changed
- `NukeExecution.detonate`: reduced call overhead/allocations by caching
`mg`/`config`, avoiding repeated lookups, and using allocation-free
loops (no `forEach` closures) in the diminishing-effect pass.
- `WarshipExecution.findTargetUnit`: replaced allocate+sort flow with
single-pass best-target selection.
- `UnitGrid.nearbyUnits`: reduced call overhead and allocations via
single-type fast path and cached query coordinates.
- `PlayerImpl.units`: added fast paths for common small-arity type
queries (1-3 unit types).
- `DefaultConfig.unitInfo`: cached `UnitInfo` objects per `UnitType` to
avoid repeated object/closure creation.
- `SAMLauncherExecution` targeting: removed sort churn and streamlined
target selection with single-pass hydrogen prioritization.



### Rebase
- One conflict was resolved in `NukeExecution.detonate` by keeping
`main`'s diminishing-effect-per-impacted-tile behavior, while retaining
the allocation-reduction refactors.


## 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-19 19:01:12 -06:00
committed by GitHub
parent c235debb57
commit f6a08e16db
6 changed files with 282 additions and 137 deletions
+33 -24
View File
@@ -252,28 +252,31 @@ export class NukeExecution implements Execution {
throw new Error("Not initialized");
}
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
const mg = this.mg;
const config = mg.config();
const magnitude = config.nukeMagnitudes(this.nuke.type());
const toDestroy = this.tilesToDestroy();
// Retrieve all impacted players and the number of tiles
const tilesPerPlayers = new Map<Player, number>();
for (const tile of toDestroy) {
const owner = this.mg.owner(tile);
const owner = mg.owner(tile);
if (owner.isPlayer()) {
owner.relinquish(tile);
const numTiles = tilesPerPlayers.get(owner);
tilesPerPlayers.set(owner, numTiles === undefined ? 1 : numTiles + 1);
tilesPerPlayers.set(owner, (tilesPerPlayers.get(owner) ?? 0) + 1);
}
if (this.mg.isLand(tile)) {
this.mg.setFallout(tile, true);
if (mg.isLand(tile)) {
mg.setFallout(tile, true);
}
}
// Then compute the explosion effect on each player
for (const [player, numImpactedTiles] of tilesPerPlayers) {
const config = this.mg.config();
const tilesBeforeNuke = player.numTilesOwned() + numImpactedTiles;
const transportShips = player.units(UnitType.TransportShip);
const outgoingAttacks = player.outgoingAttacks();
const maxTroops = config.maxTroops(player);
// nukeDeathFactor could compute the complete fallout in a single call instead
for (let i = 0; i < numImpactedTiles; i++) {
@@ -287,39 +290,45 @@ export class NukeExecution implements Execution {
maxTroops,
),
);
player.outgoingAttacks().forEach((attack) => {
for (const attack of outgoingAttacks) {
const attackTroops = attack.troops();
const deaths = config.nukeDeathFactor(
this.nukeType,
attack.troops(),
attackTroops,
numTilesLeft,
maxTroops,
);
attack.setTroops(attack.troops() - deaths);
});
transportShips.forEach((unit) => {
attack.setTroops(attackTroops - deaths);
}
for (const unit of transportShips) {
const unitTroops = unit.troops();
const deaths = config.nukeDeathFactor(
this.nukeType,
unit.troops(),
unitTroops,
numTilesLeft,
maxTroops,
);
unit.setTroops(unit.troops() - deaths);
});
unit.setTroops(unitTroops - deaths);
}
}
}
const outer2 = magnitude.outer * magnitude.outer;
for (const unit of this.mg.units()) {
const dst = this.dst;
const destroyer = this.player;
for (const unit of mg.units()) {
const type = unit.type();
if (
unit.type() !== UnitType.AtomBomb &&
unit.type() !== UnitType.HydrogenBomb &&
unit.type() !== UnitType.MIRVWarhead &&
unit.type() !== UnitType.MIRV &&
unit.type() !== UnitType.SAMMissile
type === UnitType.AtomBomb ||
type === UnitType.HydrogenBomb ||
type === UnitType.MIRVWarhead ||
type === UnitType.MIRV ||
type === UnitType.SAMMissile
) {
if (this.mg.euclideanDistSquared(this.dst, unit.tile()) < outer2) {
unit.delete(true, this.player);
}
continue;
}
if (mg.euclideanDistSquared(dst, unit.tile()) < outer2) {
unit.delete(true, destroyer);
}
}