Skip to content

Refactor: Reuse OutlineFilters to fix allocation issue and potential GC overhead in StructureIconsLayer#3339

Open
Skigim wants to merge 3 commits intoopenfrontio:mainfrom
Skigim:perf/reuse-outline-filters
Open

Refactor: Reuse OutlineFilters to fix allocation issue and potential GC overhead in StructureIconsLayer#3339
Skigim wants to merge 3 commits intoopenfrontio:mainfrom
Skigim:perf/reuse-outline-filters

Conversation

@Skigim
Copy link
Contributor

@Skigim Skigim commented Mar 3, 2026

This PR is the second slice of updates addressing #3207 by optimizing how we draw visual outlines for structures in StructureIconsLayer.ts.

The Problem:
Currently we create new outline filters instead of reusing them, which adds unnecessary allocations and some extra GC work.

The Fix:
The goal is for this to fix aforementioned allocation issue in [StructureIconsLayer] by reusing [OutlineFilter] instances instead.

In theory that should help performance a little by cutting some unnecessary allocation work, but the effect is small enough that I could not measure it reliably with browser profiling.

  • Pull outline filters into constants at the top of the file and initialize them once after imports.
  • Replace inline filter creation throughout the file with references to those shared constants.

Please complete the following:

  • I have added screenshots for all UI updates (N/A - no UI changes included)
  • I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A - no new text)
  • I have added relevant tests to the test directory (N/A - change is very limited and existing test cover the file already)
  • 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:

skigim

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Walkthrough

Replaces inline new OutlineFilter(...) usages in src/client/graphics/layers/StructureIconsLayer.ts with three shared constants: FILTER_OUTLINE_RED, FILTER_OUTLINE_GREEN, and FILTER_OUTLINE_WHITE, used for ghost outlines, upgrade indicators, and hidden-structure highlights. No public APIs changed.

Changes

Cohort / File(s) Summary
Outline Filter Constants Consolidation
src/client/graphics/layers/StructureIconsLayer.ts
Adds three shared OutlineFilter constants (FILTER_OUTLINE_RED, FILTER_OUTLINE_GREEN, FILTER_OUTLINE_WHITE) and replaces prior inline new OutlineFilter(...) constructions across rendering paths for ghost outlines, upgrade visuals, and hidden-structure highlights.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related issues

  • Issue #3207: Matches this PR's consolidation of red/green/white OutlineFilter instances in StructureIconsLayer.ts.

Poem

Three outlines march in tidy rows,
Red, green, white where the ghost light glows,
Replaced the inline, now constants sing,
Simple lines make the renderings spring,
Small change, neat code — a quiet win.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately describes the main change: refactoring to reuse OutlineFilter constants instead of creating new instances, which addresses the allocation and GC overhead issue mentioned in the PR objectives.
Description check ✅ Passed The PR description clearly explains the problem (unnecessary OutlineFilter allocations), the solution (reusing filter instances via constants), and the expected outcome (reduced GC overhead).

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

@Skigim
Copy link
Contributor Author

Skigim commented Mar 3, 2026

@scamiv

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 3, 2026
@scamiv
Copy link
Contributor

scamiv commented Mar 3, 2026

didn't have time to look deeper, but from a quick glance:
the change in itself seems to make sense from a code structure point of view.
it makes me wonder thou, is the filter actually reapplied each time or do we already cache the result up the chain?
also wouldn't claim this to be a perf improvement unless its actually very hot.

@Skigim
Copy link
Contributor Author

Skigim commented Mar 3, 2026

didn't have time to look deeper, but from a quick glance: the change in itself seems to make sense from a code structure point of view. it makes me wonder thou, is the filter actually reapplied each time or do we already cache the result up the chain? also wouldn't claim this to be a perf improvement unless its actually very hot.

So from what I can tell, the pixi library builds a programCache that covers the GPU side, but the js objects aren't cached on the CPU end so GC is still getting a workout with the memory overload.

I'll run profiler when I get home to see if there's a substantial benefit - if not I'll edit title to refactor.

@Skigim Skigim changed the title Perf: Reuse OutlineFilters to reduce lategame GC overhead in StructureIconsLayer Refactor/Perf: Reuse OutlineFilters to reduce lategame GC overhead in StructureIconsLayer Mar 6, 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 (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

335-365: This still creates new filter arrays in the hot path.

The OutlineFilter objects are reused now, but each filters = [FILTER_OUTLINE_*] here still allocates a fresh array, and the repeated writes can keep some GC pressure in the exact path this PR is trying to cool down. A small helper that skips no-op assignments and reuses the common filter-array values would make the perf win more complete.

Also applies to: 640-641

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/StructureIconsLayer.ts` around lines 335 - 365,
The code repeatedly assigns new one-element arrays to sprite.container.filters
(e.g., this.ghostUnit.container.filters = [FILTER_OUTLINE_RED] and similar for
FILTER_OUTLINE_GREEN), causing garbage churn; add reusable const arrays like
FILTERS_OUTLINE_RED = [FILTER_OUTLINE_RED] and FILTERS_OUTLINE_GREEN =
[FILTER_OUTLINE_GREEN] and a small helper method (e.g., setFilters(container:
PIXI.Container, filters: readonly any[])) that checks if container.filters ===
filters and only assigns when different, then replace all inline assignments in
StructureIconsLayer (including the ghostUnit, potentialUpgrade.iconContainer,
potentialUpgrade.dotContainer and the other occurrences mentioned) to call
setFilters with the shared arrays to avoid allocating fresh arrays on the hot
path.
🤖 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/client/graphics/layers/StructureIconsLayer.ts`:
- Around line 335-365: The code repeatedly assigns new one-element arrays to
sprite.container.filters (e.g., this.ghostUnit.container.filters =
[FILTER_OUTLINE_RED] and similar for FILTER_OUTLINE_GREEN), causing garbage
churn; add reusable const arrays like FILTERS_OUTLINE_RED = [FILTER_OUTLINE_RED]
and FILTERS_OUTLINE_GREEN = [FILTER_OUTLINE_GREEN] and a small helper method
(e.g., setFilters(container: PIXI.Container, filters: readonly any[])) that
checks if container.filters === filters and only assigns when different, then
replace all inline assignments in StructureIconsLayer (including the ghostUnit,
potentialUpgrade.iconContainer, potentialUpgrade.dotContainer and the other
occurrences mentioned) to call setFilters with the shared arrays to avoid
allocating fresh arrays on the hot path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 508f6847-9e59-4f54-9678-8f3234af88e4

📥 Commits

Reviewing files that changed from the base of the PR and between d436c26 and 5389677.

📒 Files selected for processing (1)
  • src/client/graphics/layers/StructureIconsLayer.ts

@Skigim Skigim changed the title Refactor/Perf: Reuse OutlineFilters to reduce lategame GC overhead in StructureIconsLayer Refactor: Reuse OutlineFilters to reduce lategame GC overhead in StructureIconsLayer Mar 8, 2026
# Conflicts:
#	src/client/graphics/layers/StructureIconsLayer.ts
@Skigim Skigim marked this pull request as ready for review March 8, 2026 06:12
@Skigim Skigim requested a review from a team as a code owner March 8, 2026 06:12
Copilot AI review requested due to automatic review settings March 8, 2026 06:12
@Skigim Skigim changed the title Refactor: Reuse OutlineFilters to reduce lategame GC overhead in StructureIconsLayer Refactor: Reuse OutlineFilters to fix allocation issue and potential GC overhead in StructureIconsLayer Mar 8, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors StructureIconsLayer.ts to reduce late-game GC churn by reusing pixi-filters OutlineFilter instances instead of repeatedly allocating new ones during ghost/visibility updates.

Changes:

  • Introduces shared red/green/white OutlineFilter constants initialized once at module load.
  • Replaces per-use new OutlineFilter(...) allocations with references to the shared filter instances.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

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 (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

350-380: You still allocate fresh filter arrays in the hot branches.

The filter objects are reused now, but each [FILTER_OUTLINE_*] still creates a short-lived array. If profiling still shows GC here, hoisting the one-item arrays too would remove the last new allocations from these assignments.

♻️ Possible follow-up
 const FILTER_OUTLINE_RED = new OutlineFilter({
   thickness: 2,
   color: "rgba(255, 0, 0, 1)",
 });
 const FILTER_OUTLINE_GREEN = new OutlineFilter({
   thickness: 2,
   color: "rgba(0, 255, 0, 1)",
 });
 const FILTER_OUTLINE_WHITE = new OutlineFilter({
   thickness: 2,
   color: "rgb(255, 255, 255)",
 });
+const FILTERS_OUTLINE_RED = [FILTER_OUTLINE_RED];
+const FILTERS_OUTLINE_GREEN = [FILTER_OUTLINE_GREEN];
+const FILTERS_OUTLINE_WHITE = [FILTER_OUTLINE_WHITE];
-          this.ghostUnit.container.filters = [FILTER_OUTLINE_RED];
+          this.ghostUnit.container.filters = FILTERS_OUTLINE_RED;-            this.potentialUpgrade.iconContainer.filters = [
-              FILTER_OUTLINE_GREEN,
-            ];
-            this.potentialUpgrade.dotContainer.filters = [FILTER_OUTLINE_GREEN];
+            this.potentialUpgrade.iconContainer.filters = FILTERS_OUTLINE_GREEN;
+            this.potentialUpgrade.dotContainer.filters = FILTERS_OUTLINE_GREEN;-          this.ghostUnit.container.filters = [FILTER_OUTLINE_RED];
+          this.ghostUnit.container.filters = FILTERS_OUTLINE_RED;-        render.iconContainer.filters = [FILTER_OUTLINE_WHITE];
-        render.dotContainer.filters = [FILTER_OUTLINE_WHITE];
+        render.iconContainer.filters = FILTERS_OUTLINE_WHITE;
+        render.dotContainer.filters = FILTERS_OUTLINE_WHITE;

Also applies to: 694-695

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/StructureIconsLayer.ts` around lines 350 - 380,
The code still creates new one-item filter arrays inline (e.g., assignments like
this.ghostUnit.container.filters = [FILTER_OUTLINE_RED],
this.potentialUpgrade.iconContainer.filters = [FILTER_OUTLINE_GREEN], and
dotContainer.filters = [FILTER_OUTLINE_GREEN]), causing short-lived allocations;
fix by hoisting these one-item arrays into reusable constants (e.g.,
OUTLINE_RED_FILTER_ARRAY, OUTLINE_GREEN_FILTER_ARRAY) and use those constants in
StructureIconsLayer where you set container.filters and dotContainer.filters
(also update the similar occurrences around the other noted location at lines
694-695) so the same array instances are reused instead of allocating each time.
🤖 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/client/graphics/layers/StructureIconsLayer.ts`:
- Around line 350-380: The code still creates new one-item filter arrays inline
(e.g., assignments like this.ghostUnit.container.filters = [FILTER_OUTLINE_RED],
this.potentialUpgrade.iconContainer.filters = [FILTER_OUTLINE_GREEN], and
dotContainer.filters = [FILTER_OUTLINE_GREEN]), causing short-lived allocations;
fix by hoisting these one-item arrays into reusable constants (e.g.,
OUTLINE_RED_FILTER_ARRAY, OUTLINE_GREEN_FILTER_ARRAY) and use those constants in
StructureIconsLayer where you set container.filters and dotContainer.filters
(also update the similar occurrences around the other noted location at lines
694-695) so the same array instances are reused instead of allocating each time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82164b00-f787-49d1-a67c-15ec2b419337

📥 Commits

Reviewing files that changed from the base of the PR and between 5389677 and 3b4caa8.

📒 Files selected for processing (1)
  • src/client/graphics/layers/StructureIconsLayer.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

3 participants