Skip to content

bugfix(shell): Prevent menu crashing by avoiding initializing layouts during teardown - #3158

Merged
xezon merged 4 commits into
TheSuperHackers:mainfrom
CryoTheRenegade:bugfix/2777-shell-teardown-init
Aug 21, 2026
Merged

bugfix(shell): Prevent menu crashing by avoiding initializing layouts during teardown#3158
xezon merged 4 commits into
TheSuperHackers:mainfrom
CryoTheRenegade:bugfix/2777-shell-teardown-init

Conversation

@CryoTheRenegade

Copy link
Copy Markdown

Fixes #2777. Shell teardown now suppresses initialization of newly uncovered layouts while retaining each layout’s shutdown callback. This prevents the full-screen save/load menu from accessing GameState after it has been destroyed when closing the game after loading a save. I reproduced the issue with a Zero Hour skirmish save, where the process exited with code 2816 before the change and 0 afterward.

@CryoTheRenegade
CryoTheRenegade force-pushed the bugfix/2777-shell-teardown-init branch from 6c825d4 to 024478e Compare August 16, 2026 00:22
@CryoTheRenegade
CryoTheRenegade marked this pull request as ready for review August 16, 2026 17:00
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Shell teardown by suppressing layout init on immediate pop

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Add option to suppress init when popping layouts immediately.
• Prevent uncovered layouts from initializing during Shell teardown.
• Avoid post-destroy GameState access when exiting after loading a save.
Diagram

graph TD
  A["Shell::deconstruct()"] --> B["popImmediate(TRUE)"] --> C["doPop(suppressInit)"]
  B --> F["WindowLayout::runShutdown()"]
  C -- "if !suppressInit" --> D["Init uncovered layout"] --> E[("GameState")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Internal teardown flag (no API parameter)
  • ➕ Avoids expanding the public Shell API surface
  • ➕ Prevents misuse by callers passing the wrong suppressInit value
  • ➕ Keeps lifecycle policy centralized within Shell
  • ➖ Introduces implicit state that can be harder to reason about in debugging
  • ➖ Requires careful reset semantics if Shell can be reused/reconstructed
2. Split doPop into pop + explicit re-init step
  • ➕ Makes init behavior explicit and easier to audit at call sites
  • ➕ Reduces chance of hidden side effects during pop
  • ➖ Larger refactor with more call sites impacted
  • ➖ Higher risk of regressions in UI navigation flows

Recommendation: The chosen approach (a defaulted suppressInit parameter on popImmediate forwarded into doPop) is a pragmatic, low-risk fix that targets the teardown path without broad refactoring. If future call sites need similar behavior, consider evolving this into an internal "isDestroying" flag or a clearly named API (e.g., popImmediateWithoutInit) to reduce accidental misuse.

Files changed (2) +5 / -4

Bug fix (2) +5 / -4
Shell.hAdd suppressInit option to Shell::popImmediate() +1/-1

Add suppressInit option to Shell::popImmediate()

• Extends the popImmediate() declaration to accept an optional suppressInit flag (default FALSE). Updates the comment to clarify that init of the uncovered layout can be suppressed.

Core/GameEngine/Include/GameClient/Shell.h

Shell.cppSuppress uncovered-layout init during Shell teardown +4/-3

Suppress uncovered-layout init during Shell teardown

• Updates Shell::deconstruct() to call popImmediate(TRUE) so uncovered layouts do not initialize while destroying the shell. Threads the new suppressInit parameter through popImmediate() into doPop(), preserving shutdown behavior while avoiding teardown-time init side effects.

Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 16, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Informational

1. Flag semantic mismatch ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Shell::popImmediate(Bool suppressInit) forwards suppressInit into doPop(Bool impendingPush), where
the parameter name/meaning differs (it controls whether newTop->runInit runs). This semantic
mismatch is introduced by the PR and makes the public API intent harder to follow at the call site.
Code

Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp[452]

+	doPop( suppressInit );
Evidence
The new API parameter is named suppressInit, but it is passed into doPop's impendingPush parameter;
doPop uses that boolean to gate whether it calls runInit on the newly exposed layout. This works
today but the mismatch is introduced by this PR and reduces readability/maintainability.

Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp[428-456]
Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp[689-714]
Core/GameEngine/Include/GameClient/Shell.h[131-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`popImmediate(Bool suppressInit)` passes its new flag directly into `doPop(Bool impendingPush)`. Although the polarity currently matches the desired behavior (TRUE => skip `newTop->runInit`), the different naming/abstraction (`suppressInit` vs `impendingPush`) is confusing and brittle.
### Issue Context
`doPop` currently uses its boolean to decide whether to run init on the newly exposed top layout. The PR repurposes this parameter by forwarding `suppressInit` positionally, which obscures intent.
### Fix Focus Areas
- Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp[428-714]
- Core/GameEngine/Include/GameClient/Shell.h[131-170]
### Suggested approach
Pick one:
1) Rename the `doPop` parameter (declaration + definition) to reflect what it actually controls (e.g., `suppressInit` or `runInit`), and update call sites accordingly.
2) Keep `doPop(impendingPush)` but make the mapping explicit in `popImmediate`, e.g.:
- `doPop(/*impendingPush=*/suppressInit);` (or)
- `const Bool impendingPush = suppressInit; doPop(impendingPush);`
This preserves behavior while making intent unambiguous.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp Outdated
Comment thread Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp Outdated

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code looks logical.

Comment thread Core/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp Outdated
Comment thread Core/GameEngine/Include/GameClient/Shell.h Outdated
@xezon xezon added GUI For graphical user interface Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Crash This is a crash, very bad labels Aug 20, 2026
@xezon xezon changed the title fix(shell): Avoid initializing layouts during teardown bugfix(shell): Avoid initializing layouts during teardown Aug 20, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
@xezon xezon changed the title bugfix(shell): Avoid initializing layouts during teardown bugfix(shell): Prevent menu crashing by avoiding initializing layouts during teardown Aug 21, 2026
@xezon
xezon merged commit 5b22a6c into TheSuperHackers:main Aug 21, 2026
16 checks passed
@CryoTheRenegade
CryoTheRenegade deleted the bugfix/2777-shell-teardown-init branch August 21, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Crash This is a crash, very bad Gen Relates to Generals GUI For graphical user interface Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash when closing game using window X button

3 participants