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
+49 -2
View File
@@ -214,11 +214,58 @@ export class PlayerImpl implements Player {
}
units(...types: UnitType[]): Unit[] {
if (types.length === 0) {
const len = types.length;
if (len === 0) {
return this._units;
}
// Fast paths for common small arity calls to avoid Set allocation.
if (len === 1) {
const t0 = types[0]!;
const out: Unit[] = [];
for (const u of this._units) {
if (u.type() === t0) out.push(u);
}
return out;
}
if (len === 2) {
const t0 = types[0]!;
const t1 = types[1]!;
if (t0 === t1) {
const out: Unit[] = [];
for (const u of this._units) {
if (u.type() === t0) out.push(u);
}
return out;
}
const out: Unit[] = [];
for (const u of this._units) {
const t = u.type();
if (t === t0 || t === t1) out.push(u);
}
return out;
}
if (len === 3) {
const t0 = types[0]!;
const t1 = types[1]!;
const t2 = types[2]!;
// Keep semantics identical for duplicates in types by using direct comparisons.
const out: Unit[] = [];
for (const u of this._units) {
const t = u.type();
if (t === t0 || t === t1 || t === t2) out.push(u);
}
return out;
}
const ts = new Set(types);
return this._units.filter((u) => ts.has(u.type()));
const out: Unit[] = [];
for (const u of this._units) {
if (ts.has(u.type())) out.push(u);
}
return out;
}
private numUnitsConstructed: Partial<Record<UnitType, number>> = {};
+50 -16
View File
@@ -140,29 +140,63 @@ export class UnitGrid {
includeUnderConstruction: boolean = false,
): Array<{ unit: Unit | UnitView; distSquared: number }> {
const nearby: Array<{ unit: Unit | UnitView; distSquared: number }> = [];
const gm = this.gm;
const x = gm.x(tile);
const y = gm.y(tile);
const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange(
tile,
searchRange,
);
const rangeSquared = searchRange * searchRange;
const typeSet = Array.isArray(types) ? new Set(types) : new Set([types]);
// `Array.isArray` does not reliably narrow `readonly T[]` in TS, so use a
// cheap runtime check that narrows correctly for our string-backed UnitType.
if (typeof types !== "string") {
for (let cy = startGridY; cy <= endGridY; cy++) {
for (let cx = startGridX; cx <= endGridX; cx++) {
const cell = this.grid[cy][cx];
for (const type of types) {
const unitSet = cell.get(type);
if (unitSet === undefined) continue;
for (const unit of unitSet) {
if (!unit.isActive()) continue;
// Exclude units under construction by default (e.g., defense posts being built)
// But include them for spacing checks
if (!includeUnderConstruction && unit.isUnderConstruction())
continue;
const unitTile = unit.tile();
const dx = gm.x(unitTile) - x;
const dy = gm.y(unitTile) - y;
const distSquared = dx * dx + dy * dy;
if (distSquared > rangeSquared) continue;
const value = { unit, distSquared };
if (predicate !== undefined && !predicate(value)) continue;
nearby.push(value);
}
}
}
}
return nearby;
}
const type = types;
for (let cy = startGridY; cy <= endGridY; cy++) {
for (let cx = startGridX; cx <= endGridX; cx++) {
for (const type of typeSet) {
const unitSet = this.grid[cy][cx].get(type);
if (unitSet === undefined) continue;
for (const unit of unitSet) {
if (!unit.isActive()) continue;
// Exclude units under construction by default (e.g., defense posts being built)
// But include them for spacing checks
if (!includeUnderConstruction && unit.isUnderConstruction())
continue;
const distSquared = this.squaredDistanceFromTile(unit, tile);
if (distSquared > rangeSquared) continue;
const value = { unit, distSquared };
if (predicate !== undefined && !predicate(value)) continue;
nearby.push(value);
}
const unitSet = this.grid[cy][cx].get(type);
if (unitSet === undefined) continue;
for (const unit of unitSet) {
if (!unit.isActive()) continue;
// Exclude units under construction by default (e.g., defense posts being built)
// But include them for spacing checks
if (!includeUnderConstruction && unit.isUnderConstruction()) continue;
const unitTile = unit.tile();
const dx = gm.x(unitTile) - x;
const dy = gm.y(unitTile) - y;
const distSquared = dx * dx + dy * dy;
if (distSquared > rangeSquared) continue;
const value = { unit, distSquared };
if (predicate !== undefined && !predicate(value)) continue;
nearby.push(value);
}
}
}