mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-28 12:50:28 +00:00
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:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user