feat: Implement ALT + F4 and Window X button to close game#2336
feat: Implement ALT + F4 and Window X button to close game#2336githubawn wants to merge 22 commits intoTheSuperHackers:mainfrom
Conversation
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | Replaced inline ESC-checking loops with GameClient::isMovieAbortRequested() calls; added guard for non-ready frame before frameDecompress()/frameRender() on early abort; added isMovieAbortRequested() check to the loading-progress loop. |
| Generals/Code/GameEngine/Include/GameLogic/GameLogic.h | Adds quit(), isQuitToDesktopRequested(), tryStartNewGame(), and m_quitToDesktopAfterMatch member — all appropriately scoped (getter is public, member is private, tryStartNewGame is private). |
| Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Adds centralized quit(), QuitGameException mechanism for loading abort, and tryStartNewGame() wrapper; correctly guards script-engine and loading-state checks; properly initializes m_quitToDesktopAfterMatch in constructor. |
| Generals/Code/GameEngine/Source/GameClient/GameClient.cpp | New isMovieAbortRequested() consolidates ESC + window-close detection cleanly; trailing whitespace on line 777; TheMessageStream dereferenced without null check (consistent with rest of codebase but diverges from the TheKeyboard pattern in the same function). |
| Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp | Greatly simplified by delegating to TheGameLogic->quit(); destroyQuitMenu() is now called after quit() (reversed from original), but safe because exitGame() only queues MSG_CLEAR_GAME_DATA — no synchronous window destruction occurs. |
| Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp | Moved MSG_META_DEMO_INSTANT_QUIT handler outside the #if RTS_DEBUG guard; now reads force boolean argument and delegates to quit(); registered as a system message so it propagates during loading. |
| Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp | Adds m_quitToDesktopAfterMatch check in clearGameData() to trigger setQuitting(TRUE) and resets the flag after use — correct handling of the deferred desktop-quit path. |
| Generals/Code/Main/WinMain.cpp | WM_CLOSE now appends MSG_META_DEMO_INSTANT_QUIT with a boolean force flag instead of hard-quitting; adds TheGameEngine null guard and ThePlayerList initialization check; Alt+F4 detection via GetAsyncKeyState has a minor reliability concern documented in review comment. |
| Generals/Code/GameEngine/Include/Common/MessageStream.h | Moves MSG_META_DEMO_INSTANT_QUIT before the debug-only block, making it a regular non-debug message; enum shift is safe per developer confirmation that MSG_META_* messages are never serialized. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["WM_CLOSE / WM_QUERYENDSESSION\n(WinMain.cpp)"] --> B{TheMessageStream\n& ThePlayerList?}
B -- Yes --> C["Append MSG_META_DEMO_INSTANT_QUIT\n+ appendBooleanArgument(isAltF4)"]
B -- No --> D["TheGameEngine->setQuitting(TRUE)"]
C --> E["CommandXlat: MSG_META_DEMO_INSTANT_QUIT\n→ GameLogic::quit(TRUE, force)"]
F["QuitMenu: exitQuitMenu()"] --> G["GameLogic::quit(FALSE)"]
H["QuitMenu: quitToDesktopQuitMenu()"] --> I["GameLogic::quit(TRUE)"]
G --> J{isInGame?}
I --> J
E --> J
J -- No --> K["setQuitting if toDesktop\nsetClientQuiet"]
J -- Yes --> L{isInInteractiveGame?}
L -- Yes, force=FALSE\nnot loading --> M["ToggleQuitMenu()\n(show quit dialog)"]
L -- Yes --> N{Multiplayer &\nnot skirmish &\nnot sandbox?}
N -- Yes --> O["MSG_SELF_DESTRUCT"]
N -- No --> P
O --> P["stopRecording\nsetGamePaused(FALSE)\nunfreezeTime"]
P --> Q{toDesktop?}
Q -- Yes, Multiplayer --> R["m_quitToDesktopAfterMatch=TRUE\nupdateLoadProgress → QuitGameException\nclearGameData → setQuitting(TRUE)"]
Q -- Yes, Single-player --> S["clearGameData()\nsetQuitting(TRUE)"]
Q -- No --> T["exitGame()\n→ MSG_CLEAR_GAME_DATA (deferred)"]
U["isMovieAbortRequested()\n(GameClient.cpp)"] --> V{ESC key?}
V -- Yes --> W["return TRUE\n(abort movie)"]
V -- No --> X["serviceWindowsOS()\npropagateMessages()"]
X --> Y{getQuitting or\nm_quitToDesktopAfterMatch?}
Y -- Yes --> W
Y -- No --> Z["return FALSE\n(continue)"]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameClient/GameClient.cpp
Line: 777
Comment:
**Trailing whitespace on blank line**
Line 777 (and the corresponding line 814 in `GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp`) contains trailing whitespace. This is a minor inconsistency with the surrounding code style.
```suggestion
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: Generals/Code/Main/WinMain.cpp
Line: 376-378
Comment:
**`GetAsyncKeyState` polling for Alt+F4 detection may be unreliable in queued-close scenarios**
`GetAsyncKeyState` returns the physical key state at the moment the call executes — not the key state at the moment `WM_CLOSE` was originally generated. In the normal case (user presses Alt+F4), Windows dispatches `WM_CLOSE` synchronously while the keys are still held, so this works correctly.
However, if `WM_CLOSE` arrives from a `PostMessage` / `SendMessage` call (e.g., a queued close from another context) while the user happens to be holding Alt for unrelated reasons, `isAltF4` will be `TRUE` even though the close was not initiated by Alt+F4. This would trigger a forced quit (no quit menu shown) instead of the normal graceful quit-menu path.
The same pattern appears at `GeneralsMD/Code/Main/WinMain.cpp:393-395`.
Consider using the `wParam`/`lParam` passed to `WM_SYSCOMMAND` or checking `SC_CLOSE` before `WM_CLOSE` as a more reliable signal, or at minimum documenting the known limitation with a comment.
How can I resolve this? If you propose a fix, please make it concise.Reviews (16): Last reviewed commit: "Remove TheMessageStream->propagateMessag..." | Re-trigger Greptile
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp
Outdated
Show resolved
Hide resolved
|
Needs rebase. My initial thought on this, without looking at code, is that it will make it easier for players to disconnect from a Multiplayer match - by accident or intentional - so this feature must not work during a Multiplayer Match. |
|
Will rebase soon. The multiplayer behavior was designed based on your comment from #1356 (comment). It also adds safe abort logic so Alt+F4 works consistently everywhere addressing helmutbuhler's #1356 (comment) concern about the button only working in some situations. |
|
Thank you for implementing this. I did a few quick tests and the current implementation appears to work well. |
843b370 to
4fda496
Compare
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
2d896d5 to
036ed84
Compare
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
xezon
left a comment
There was a problem hiding this comment.
It would have been easier to review if this change just contained Core+GeneralsMD edits for starters.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
3a3c926 to
6f2b0b8
Compare
a2166e4 to
c6ee106
Compare
Additional Comments (1)
The original code called The identical implementation in Prompt To Fix With AIThis is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameClient/GameClient.cpp
Line: 803-806
Comment:
**`TheKeyboard->UPDATE()` called on every invocation of `isMovieAbortRequested()`**
`isMovieAbortRequested()` is called in tight video playback loops (e.g. spinning on `isFrameReady()` with `Sleep(1)` between iterations). Each call unconditionally invokes `TheKeyboard->UPDATE()`, which may accumulate keyboard state incorrectly or cause events to be double-processed when the function is called at high frequency within a single logical frame.
The original code called `UPDATE()` once per outer loop iteration for a specific keyboard check. The refactored helper should either guard against repeated calls (e.g. a static frame counter check) or the callers should call it once per outer iteration rather than inside the innermost spin loop.
The identical implementation in `GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp` has the same concern.
How can I resolve this? If you propose a fix, please make it concise. |
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp
Show resolved
Hide resolved
added comments moved QuitGameException
|
Greptile is still complaining about "the SinglePlayerLoadScreen min-spec wait loop in LoadScreen.cpp was not given the same isMovieAbortRequested() + frame-ready guard that ChallengeLoadScreen received, leaving a small hang window during Generals-only min-spec map loads." |
|
Greptile didn't update the first comment and I don't think I can tag it as it doesn't give me the tag pop-up. Let me try: |
Summary of PR #2336: Alt+F4 and Window X Button SupportThis PR implements graceful exit handling for Alt+F4 and the window close button, addressing issue #1356. The changes are split across two commits: Commit 1: Centralized Quit Logic
Commit 2: Graceful Exit During Loading & Movies
Technical Details
The implementation ensures clean shutdown in all game states (main menu, loading, in-game, during movies) while properly handling multiplayer, recording, and script engine cleanup. |
|
@greptileai Include "Key findings:" and "Confidence Score:". |
|
Key findings:
Minor observations:
Confidence Score: 90/100 This is high-quality work. The architecture is sound, edge cases are handled, and the refactoring meaningfully improves maintainability. The 10% deduction is purely because extensive quit scenarios (loading screens, movies, multiplayer, recording, etc.) require thorough testing to ensure no regressions. |
GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp
Outdated
Show resolved
Hide resolved
fixes accidentally revert
| { | ||
| TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); | ||
| // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. | ||
| // If not, we're likely in early startup/late shutdown, so we hard-quit instead. |
There was a problem hiding this comment.
What the comment describes is unintuitive. Where/how have you seen this requirement?
There was a problem hiding this comment.
this was added after this comment
|
|
||
| //This method used to disable quitting. We just put up the options screen instead. | ||
| //TheMessageStream->appendMessage(GameMessage::MSG_META_OPTIONS); | ||
| if (TheMessageStream && ThePlayerList) |
There was a problem hiding this comment.
This one is also testing ThePlayerList. I still do not understand it. It looks like a "code smell".
There was a problem hiding this comment.
This is for the exact same reason as above. It prevents the engine from crashing during an early exit since ThePlayerList isn't ready.
|
Loading a savegame in a match causes a quit, back to draft |
|
@greptileai when i load another savegame from an already loaded match the game now quits the game Should i remove "TheMessageStream->propagateMessages();" from LoadScreen.cpp and or Gameclient.cpp? |
|
Yes, remove Keep it in GameClient.cpp - that one is specifically for detecting quit requests during movie playback (the intended feature). The issue is that during a savegame load, Removing it from LoadScreen.cpp prevents processing messages during normal loading while still allowing the quit detection to work during movies via |
Fix leftover quit state
Fixes #1356
Split up in 2 commits.
Previously, the quit logic was duplicated and inconsistent across several places (the quit menu, the "quit to desktop" button, and the Alt+F4 handler). This commit centralizes all of that into a single GameLogic::quit(Bool toDesktop) method. It properly handles edge cases like stopping an active recording, sending a self-destruct message in multiplayer (using a new m_quitToDesktopAfterMatch flag), unpausing/unfreezing the game before exiting. All the old scattered quit code is replaced with a simple call to TheGameLogic->quit(true/false).
This follow-up extends the graceful exit to two more situations where the game previously couldn't be closed cleanly:
During the loading screen: The load progress update now checks if a quit has been requested and throws a QuitGameException, which is caught in startNewGame() to cleanly abort the loading process mid-way.
During movies: The inline ESC-key checking that was copy-pasted in multiple video playback loops is refactored into a new GameClient::isMovieAbortRequested() method. This method now also checks for window close / Alt+F4 events (not just ESC), so closing the window during a movie no longer gets stuck. The MSG_META_DEMO_INSTANT_QUIT message is also registered as a system message so it propagates correctly even during loading.