buildableUnits perf and related refactor of StructureTypes/IsStructureType

This commit is contained in:
VariableVince
2026-02-13 00:31:08 +01:00
parent bd96bcca0d
commit a9fa5d9780
8 changed files with 57 additions and 38 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import { Execution, Game, isStructureType, Player } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { AllianceExtensionExecution } from "./alliance/AllianceExtensionExecution";
@@ -84,7 +84,7 @@ export class BotExecution implements Execution {
private deleteAllStructures() {
for (const unit of this.bot.units()) {
if (isStructureType(unit.type()) && this.bot.canDeleteUnit()) {
if (this.mg.isStructureType(unit.type()) && this.bot.canDeleteUnit()) {
this.mg.addExecution(new DeleteUnitExecution(this.bot, unit.id()));
}
}
+1 -2
View File
@@ -1,7 +1,6 @@
import {
Execution,
Game,
isStructureType,
MessageType,
Player,
TerraNullius,
@@ -333,7 +332,7 @@ export class NukeExecution implements Execution {
private redrawBuildings(range: number) {
const rangeSquared = range * range;
for (const unit of this.mg.units()) {
if (isStructureType(unit.type())) {
if (this.mg.isStructureType(unit.type())) {
if (
this.mg.euclideanDistSquared(this.dst, unit.tile()) < rangeSquared
) {
+3 -3
View File
@@ -1,5 +1,5 @@
import { NukeMagnitude } from "../configuration/Config";
import { Game, Player, StructureTypes } from "../game/Game";
import { Game, Player } from "../game/Game";
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
import { GameView } from "../game/GameView";
@@ -60,7 +60,7 @@ export function wouldNukeBreakAlliance(
const wouldDestroyAlliedStructure = game.anyUnitNearby(
targetTile,
magnitude.outer,
StructureTypes,
game.getStructureTypes(),
(unit) =>
unit.owner().isPlayer() && allySmallIds.has(unit.owner().smallID()),
);
@@ -119,7 +119,7 @@ export function listNukeBreakAlliance(
// Also check if any allied structures would be destroyed
game
.nearbyUnits(targetTile, magnitude.outer, [...StructureTypes])
.nearbyUnits(targetTile, magnitude.outer, game.getStructureTypes())
.forEach(({ unit }) =>
playersToBreakAllianceWith.add(unit.owner().smallID()),
);
@@ -5,7 +5,6 @@ import {
Player,
PlayerType,
Relation,
StructureTypes,
Unit,
UnitType,
} from "../../game/Game";
@@ -347,7 +346,7 @@ export class NationStructureBehavior {
*/
private getTotalStructureDensity(): number {
let totalStructures = 0;
for (const type of StructureTypes) {
for (const type of this.game.getStructureTypes()) {
totalStructures += this.player.units(type).length; // ignoring levels
}
const tilesOwned = this.player.numTilesOwned();
+2 -15
View File
@@ -258,21 +258,6 @@ export enum TrainType {
Carriage = "Carriage",
}
const _structureTypes: ReadonlySet<UnitType> = new Set([
UnitType.City,
UnitType.DefensePost,
UnitType.SAMLauncher,
UnitType.MissileSilo,
UnitType.Port,
UnitType.Factory,
]);
export const StructureTypes: readonly UnitType[] = [..._structureTypes];
export function isStructureType(type: UnitType): boolean {
return _structureTypes.has(type);
}
export interface OwnerComp {
owner: Player;
}
@@ -793,6 +778,8 @@ export interface Game extends GameMap {
predicate?: UnitPredicate,
includeUnderConstruction?: boolean,
): Array<{ unit: Unit; distSquared: number }>;
getStructureTypes(): UnitType[];
isStructureType(type: UnitType): boolean;
addExecution(...exec: Execution[]): void;
displayMessage(
+16
View File
@@ -97,6 +97,9 @@ export class GameImpl implements Game {
private _miniWaterGraph: AbstractGraph | null = null;
private _miniWaterHPA: AStarWaterHierarchical | null = null;
private _structureTypes: UnitType[];
private _structureTypesSet: Set<UnitType>;
constructor(
private _humans: PlayerInfo[],
private _nations: Nation[],
@@ -128,6 +131,11 @@ export class GameImpl implements Game {
);
}
this._structureTypes = Object.values(UnitType).filter(
(t) => this._config.unitInfo(t).territoryBound,
);
this._structureTypesSet = new Set(this._structureTypes);
console.log(
`[GameImpl] Constructor total: ${(performance.now() - constructorStart).toFixed(0)}ms`,
);
@@ -903,6 +911,14 @@ export class GameImpl implements Game {
}>;
}
getStructureTypes(): UnitType[] {
return this._structureTypes;
}
isStructureType(type: UnitType): boolean {
return this._structureTypesSet.has(type);
}
ref(x: number, y: number): TileRef {
return this._map.ref(x, y);
}
+16
View File
@@ -598,6 +598,9 @@ export class GameView implements GameMap {
private _map: GameMap;
private _structureTypes: UnitType[];
private _structureTypesSet: Set<UnitType>;
constructor(
public worker: WorkerClient,
private _config: Config,
@@ -626,6 +629,11 @@ export class GameView implements GameMap {
flag: nation.flag,
} satisfies PlayerCosmetics);
}
this._structureTypes = Object.values(UnitType).filter(
(t) => this._config.unitInfo(t).territoryBound,
);
this._structureTypesSet = new Set(this._structureTypes);
}
isOnEdgeOfMap(ref: TileRef): boolean {
@@ -754,6 +762,14 @@ export class GameView implements GameMap {
);
}
getStructureTypes(): UnitType[] {
return this._structureTypes;
}
isStructureType(type: UnitType): boolean {
return this._structureTypesSet.has(type);
}
myClientID(): ClientID {
return this._myClientID;
}
+16 -14
View File
@@ -30,7 +30,6 @@ import {
PlayerProfile,
PlayerType,
Relation,
StructureTypes,
Team,
TerraNullius,
Tick,
@@ -52,6 +51,8 @@ import {
} from "./TransportShipUtils";
import { UnitImpl } from "./UnitImpl";
const UNIT_TYPES = Object.freeze(Object.values(UnitType));
interface Target {
tick: Tick;
target: Player;
@@ -911,6 +912,10 @@ export class PlayerImpl implements Player {
}
public findUnitToUpgrade(type: UnitType, targetTile: TileRef): Unit | false {
if (!this.mg.config().unitInfo(type).upgradable) {
return false;
}
const range = this.mg.config().structureMinDist();
const existing = this.mg
.nearbyUnits(targetTile, range, type, undefined, true)
@@ -958,23 +963,22 @@ export class PlayerImpl implements Player {
public buildableUnits(tile: TileRef | null): BuildableUnit[] {
const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : [];
return Object.values(UnitType).map((u) => {
return UNIT_TYPES.map((u) => {
const cost = this.mg.config().unitInfo(u).cost(this.mg, this);
let canUpgrade: number | false = false;
let canBuild: TileRef | false = false;
if (!this.mg.inSpawnPhase()) {
const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile);
if (tile !== null && !this.mg.inSpawnPhase()) {
const existingUnit = this.findUnitToUpgrade(u, tile);
if (existingUnit !== false) {
canUpgrade = existingUnit.id();
}
if (tile !== null) {
canBuild = this.canBuild(u, tile, validTiles);
}
canBuild = this.canBuild(u, tile, validTiles, cost);
}
return {
type: u,
canBuild,
canUpgrade,
cost: this.mg.config().unitInfo(u).cost(this.mg, this),
cost,
overlappingRailroads:
canBuild !== false
? this.mg.railNetwork().overlappingRailroads(canBuild)
@@ -987,12 +991,13 @@ export class PlayerImpl implements Player {
unitType: UnitType,
targetTile: TileRef,
validTiles: TileRef[] | null = null,
knownCost: Gold | null = null,
): TileRef | false {
if (this.mg.config().isUnitDisabled(unitType)) {
return false;
}
const cost = this.mg.unitInfo(unitType).cost(this.mg, this);
const cost = knownCost ?? this.mg.unitInfo(unitType).cost(this.mg, this);
if (
unitType !== UnitType.MIRVWarhead &&
(!this.isAlive() || this.gold() < cost)
@@ -1054,7 +1059,7 @@ export class PlayerImpl implements Player {
const wouldHitTeammate = this.mg.anyUnitNearby(
tile,
magnitude.outer,
StructureTypes,
this.mg.getStructureTypes(),
(unit) => unit.owner().isPlayer() && this.isOnSameTeam(unit.owner()),
);
if (wouldHitTeammate) {
@@ -1133,14 +1138,11 @@ export class PlayerImpl implements Player {
}
const searchRadius = 15;
const searchRadiusSquared = searchRadius ** 2;
const types = Object.values(UnitType).filter((unitTypeValue) => {
return this.mg.config().unitInfo(unitTypeValue).territoryBound;
});
const nearbyUnits = this.mg.nearbyUnits(
tile,
searchRadius * 2,
types,
this.mg.getStructureTypes(),
undefined,
true,
);