Skip to content

Perf/small refactor: NationStructureBehavior#3237

Merged
FloPinguin merged 1 commit intomainfrom
nationsstructurebehavior-improvements
Feb 18, 2026
Merged

Perf/small refactor: NationStructureBehavior#3237
FloPinguin merged 1 commit intomainfrom
nationsstructurebehavior-improvements

Conversation

@VariableVince
Copy link
Contributor

@VariableVince VariableVince commented Feb 18, 2026

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:

  • 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:

tryout33

@VariableVince VariableVince added this to the v30 milestone Feb 18, 2026
@VariableVince VariableVince self-assigned this Feb 18, 2026
@VariableVince VariableVince requested a review from a team as a code owner February 18, 2026 22:05
@VariableVince VariableVince added Performance Performance optimization Refactor Code cleanup, technical debt, refactoring, and architecture improvements. labels Feb 18, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Walkthrough

Single-file refactoring that caches repeated game.config() lookups into local variables, simplifies upgrade validation logic by delegating checks to execution handlers, and optimizes structure density calculation using direct unit filtering.

Changes

Cohort / File(s) Summary
Config caching & reference consolidation
src/core/execution/nation/NationStructureBehavior.ts
Introduced local gameConfig and game variables to replace repeated this.game.config() calls across shouldBuildStructure, maybeSpawnStructure, and SAM-distance scoring logic, reducing redundant lookups.
Upgrade validation simplification
src/core/execution/nation/NationStructureBehavior.ts
Removed explicit canUpgradeUnit check from main upgrade trigger and relaxed validation; added early filtering in findBestStructureToUpgrade via canUpgradeUnit; replaced checks in maybeUpgradeStructure with null-guard logic, delegating validation to UpgradeStructureExecution.
Density calculation refactor
src/core/execution/nation/NationStructureBehavior.ts
Replaced manual structure aggregation in getTotalStructureDensity with single .length calculation using this.player.units(...StructureTypes).length for clarity and efficiency.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🔄 Caching whispers, lookups dance,
Local refs spin their swift romance,
Density flows through cleaner streams,
One file shines with optimized dreams! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title refers to real changes in the PR but is vague and generic (using 'some refactor/perf changes'), making it unclear what specific improvements are being made. Use a more descriptive title that highlights the main improvements, such as 'Cache game config and optimize structure upgrade checks in NationStructureBehavior' or 'Reduce redundant canUpgradeUnit checks and cache config in NationStructureBehavior'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains all the changes made to NationStructureBehavior, references related PRs, and provides specific rationale for each modification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@VariableVince VariableVince changed the title NationStructureBehavior some refactor/perf changes Perf/small refactor: NationStructureBehavio Feb 18, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/core/execution/nation/NationStructureBehavior.ts (2)

176-177: Optional: align local variable name with the config convention used elsewhere.

shouldBuildStructure names this.game.config() as gameConfig, but handleStructures (line 94) and getSaveUpTarget (line 295) both call the same value config. The name gameConfig is also mildly confusing because the next line calls gameConfig.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: hoist samRange and game.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 a samRange for each SAM before the scoring loop removes the repeated game.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: euclideanDistSquared belongs to game, not cfg; only the samRange call moves to cfg.

🤖 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.

Copy link
Contributor

@FloPinguin FloPinguin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, makes sense

@github-project-automation github-project-automation bot moved this from Triage to Final Review in OpenFront Release Management Feb 18, 2026
@FloPinguin FloPinguin added this pull request to the merge queue Feb 18, 2026
Merged via the queue into main with commit 348ccfc Feb 18, 2026
18 checks passed
@FloPinguin FloPinguin deleted the nationsstructurebehavior-improvements branch February 18, 2026 22:25
@github-project-automation github-project-automation bot moved this from Final Review to Complete in OpenFront Release Management Feb 18, 2026
@VariableVince VariableVince changed the title Perf/small refactor: NationStructureBehavio Perf/small refactor: NationStructureBehavior Feb 18, 2026
github-merge-queue bot pushed a commit that referenced this pull request Feb 18, 2026
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Performance Performance optimization Refactor Code cleanup, technical debt, refactoring, and architecture improvements.

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

2 participants

Comments