Perf/small refactor: NationStructureBehavior#3237
Conversation
WalkthroughSingle-file refactoring that caches repeated Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/core/execution/nation/NationStructureBehavior.ts (2)
176-177: Optional: align local variable name with theconfigconvention used elsewhere.
shouldBuildStructurenamesthis.game.config()asgameConfig, buthandleStructures(line 94) andgetSaveUpTarget(line 295) both call the same valueconfig. The namegameConfigis also mildly confusing because the next line callsgameConfig.gameConfig(), making the chain look redundant at first glance.♻️ Suggested rename
- const gameConfig = this.game.config(); - const { difficulty } = gameConfig.gameConfig(); + const config = this.game.config(); + const { difficulty } = config.gameConfig(); const ratios = getStructureRatios(difficulty); ... - !gameConfig.isUnitDisabled(UnitType.Port) + !config.isUnitDisabled(UnitType.Port)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/execution/nation/NationStructureBehavior.ts` around lines 176 - 177, In shouldBuildStructure replace the local variable named gameConfig with the same convention used elsewhere (e.g., config) to avoid confusion with the subsequent gameConfig.gameConfig() call; locate the assignment where this.game.config() is stored and rename that variable to config (consistent with handleStructures and getSaveUpTarget) and update the following destructure line that reads gameConfig.gameConfig() to config.gameConfig() so names are consistent and clearer.
398-418: Optional: hoistsamRangeandgame.config()out of the inner loop.
game.config().samRange(sam.level())at line 405 is called once per(structure, sam)pair, but it depends only on the SAM's level — not on the structure being scored. Precomputing asamRangefor each SAM before the scoring loop removes the repeatedgame.config()call from the inner body.♻️ Suggested optimization
+ const cfg = game.config(); + // Pre-compute per-SAM range (depends only on level, not on structure) + const samRanges = new Map<Unit, number>( + samLaunchers.map((sam) => [sam, cfg.samRange(sam.level())]), + ); + for (const structure of upgradable) { let score = 0; for (const sam of samLaunchers) { - const samRange = game.config().samRange(sam.level()); + const samRange = samRanges.get(sam)!; const samRangeSquared = samRange * samRange; - const distSquared = game.euclideanDistSquared( + const distSquared = cfg.euclideanDistSquared(Note:
euclideanDistSquaredbelongs togame, notcfg; only thesamRangecall moves tocfg.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/execution/nation/NationStructureBehavior.ts` around lines 398 - 418, The scoring loop repeatedly calls game.config().samRange(sam.level()) for each (structure, sam) pair which is redundant; precompute per-SAM ranges before iterating structures by mapping samLaunchers to an array of objects like { sam, range, rangeSquared } using game.config().samRange(sam.level()), then in the inner loop use the cached rangeSquared and sam (keeping game.euclideanDistSquared(structure.tile(), sam.tile()) as-is) so NationStructureBehavior's scoring logic (references: samLaunchers, upgradable, samRange, sam.level(), euclideanDistSquared, structure.tile(), sam.tile()) uses the precomputed values and avoids repeated game.config() calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/core/execution/nation/NationStructureBehavior.ts`:
- Around line 176-177: In shouldBuildStructure replace the local variable named
gameConfig with the same convention used elsewhere (e.g., config) to avoid
confusion with the subsequent gameConfig.gameConfig() call; locate the
assignment where this.game.config() is stored and rename that variable to config
(consistent with handleStructures and getSaveUpTarget) and update the following
destructure line that reads gameConfig.gameConfig() to config.gameConfig() so
names are consistent and clearer.
- Around line 398-418: The scoring loop repeatedly calls
game.config().samRange(sam.level()) for each (structure, sam) pair which is
redundant; precompute per-SAM ranges before iterating structures by mapping
samLaunchers to an array of objects like { sam, range, rangeSquared } using
game.config().samRange(sam.level()), then in the inner loop use the cached
rangeSquared and sam (keeping game.euclideanDistSquared(structure.tile(),
sam.tile()) as-is) so NationStructureBehavior's scoring logic (references:
samLaunchers, upgradable, samRange, sam.level(), euclideanDistSquared,
structure.tile(), sam.tile()) uses the precomputed values and avoids repeated
game.config() calls.
FloPinguin
left a comment
There was a problem hiding this comment.
thank you, makes sense
## Description: PR 6/x in effort to break up PR #3220. Follows on already merged #3237. Please see if these can be merged for v30. - **PlayerImpl**: validStructureSpawnTiles did a filter on unit types to get isTerroritoryBound units, on every call again. It read this from unit info in DefaultConfig. While having it centrally in DefaultConfig unitInfo is good for maintainability, other code uses hardcoded StructureTypes and isisStructureType from Game.ts. Which has the same purpose and thus contains the same unit types. StructureTypes and isisStructureType do need manual maintainance outside of DefaultConfig. And are more bug prone/less type safe. But, using them gives more speed compared to getting these unit types out of DefaultConfig unitInfo centrally with some cached function in GameImpl for example (tested with buildableUnits and MIRVPerf.ts). So I went with StructureTypes in validStructureSpawnTiles too. - **PlayerExecution**: now validStructureSpawnTiles no longer needs isTerritoryBound (see the point above), PlayerExecution is the last place where it was used. Replaced it for isStructureType here too (since it has the same meaning and outcome). - **Game.ts** and **DefaultConfig** unitInfo: remove the now unused _territoryBound_. As it was only used in validStructureSpawnTiles and PlayerExecution and has been replaced in both (see the two points above). ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] 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: tryout33
Description:
PR 5/x in effort to break up PR #3220. Follows on already merged #3236. Precedes #3238.
Please see if these can be merged for v30.
NationStructureBehavior:
maybeSpawnStructure: cache this.game to be used twice.
maybeSpawnStructure: instead of hardcoded ruling out Defense Post for upgrade check, check dynamically if type is upgradable. That way if defense posts ever do become upgradable, we don't run into a bug right away.
maybeUpgradeStructure: removed canUpgradeUnit check. Since it already checked this right before in findBestStructureToUpgrade, so only upgradable units are returned. And canUpgradeUnit is also checked right after in UpgradeStructureExecution. So we're going from 3 times to 2 times canUpgradeUnit, small perf win too.
findBestStructureToUpgrade: cache this.game to be used thrice.
shouldBuildStructure: cache this.game.config() to be used twice.
getTotalStructureDensity: this.player.units can handle an array of unit types to count. Input StructureTypes like this so we don't need a loop and count, and only have to get an array length once. getTotalStructureDensity needs to ignore unit levels so we can't make use of other pre-defined functions in PlayerImpl (which were created to avoid array length calls), but at least this saves a few.
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
tryout33