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 18 commits intoTheSuperHackers:mainfrom
Conversation
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | Refactored video skip to use isMovieAbortRequested() in both SinglePlayerLoadScreen and ChallengeLoadScreen loops; ChallengeLoadScreen correctly received the abort guard + post-loop frame-ready check, but the #if RTS_GENERALS min-spec fallback path in SinglePlayerLoadScreen (lines 596–602) was missed — the wait loop cannot be interrupted and frameDecompress() has no frame-ready guard. |
| Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Added centralized quit(Bool toDesktop, Bool force) method and QuitGameException-based loading abort; tryStartNewGame wrapper and m_quitToDesktopAfterMatch flag handle clean exit paths correctly; !isInSkirmishGame() guard is correctly preserved in the self-destruct check. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Mirror of Generals GameLogic.cpp changes for Zero Hour; same quit() implementation with correct skirmish/sandbox guard and loading-state checks for script engine calls. |
| Generals/Code/Main/WinMain.cpp | Replaced hard-quit WM_CLOSE handler with graceful message-based exit; correctly detects Alt+F4 vs. X-button and passes force flag; falls back to setQuitting(TRUE) during early startup/late shutdown; null checks for TheGameEngine and TheMessageStream/ThePlayerList are properly guarded. |
| GeneralsMD/Code/Main/WinMain.cpp | Zero Hour WM_CLOSE and WM_QUERYENDSESSION both updated to use message-based graceful quit with null-checks and force-flag propagation; consistent with Generals version. |
| Generals/Code/GameEngine/Source/GameClient/GameClient.cpp | New static isMovieAbortRequested() correctly consolidates ESC check, serviceWindowsOS(), and quit-flag poll; one trailing whitespace on blank line between propagateMessages() call and the if block. |
| Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp | Removed legacy #ifdef ALLOW_ALT_F4 guard (and the old #define dont_ALLOW_ALT_F4), wired MSG_META_DEMO_INSTANT_QUIT to quit(TRUE, force), and registered it as a system message so it propagates during loading. |
| Generals/Code/GameEngine/Include/GameLogic/GameLogic.h | Properly added quit() declaration, private tryStartNewGame / m_quitToDesktopAfterMatch, and isQuitToDesktopRequested() getter; startNewGame parameter renamed from loadSaveGame to saveGame which is misleadingly ambiguous. |
| Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp | Duplicate quit logic cleanly replaced with single TheGameLogic->quit() call in both exitQuitMenu and quitToDesktopQuitMenu; the quit-menu-visible guard in quit() correctly prevents double-toggling the menu. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["WM_CLOSE / Alt+F4"] -->|"WndProc"| B{"TheMessageStream\n& ThePlayerList\ninitialized?"}
B -->|"Yes"| C["Append MSG_META_DEMO_INSTANT_QUIT\n(force = isAltF4)"]
B -->|"No (early startup / late shutdown)"| D["TheGameEngine->setQuitting(TRUE)"]
C --> E["CommandTranslator::translateGameMessage"]
E --> F["TheGameLogic->quit(toDesktop=TRUE, force)"]
F --> G{"isInGame()"}
G -->|"No"| M
G -->|"Yes"| H{"isInInteractiveGame()\n& force == FALSE\n& not loading?"}
H -->|"Yes — quit menu not visible"| I["ToggleQuitMenu() → return\n(user confirms via UI)"]
I -->|"User clicks Quit to Desktop"| F2["quit(TRUE, force=FALSE)\n(menu visible → skip toggle)"]
F2 --> J
H -->|"force==TRUE or loading"| J{"toDesktop?"}
J -->|"TRUE, multiplayer"| K["m_quitToDesktopAfterMatch = TRUE\nexitGame() if not loading"]
J -->|"TRUE, single-player"| L["clearGameData() if not loading\nsetQuitting(TRUE)"]
J -->|"FALSE"| N["exitGame()"]
K --> O["updateLoadProgress throws\nQuitGameException"]
O --> P["startNewGame catch block\naborts load cleanly"]
L --> M["setClientQuiet(TRUE)"]
N --> M
P --> M
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp
Line: 599-602
Comment:
**Min-spec wait loop not interruptible — missing abort check and frame-ready guard**
`ChallengeLoadScreen::init()` received both an `isMovieAbortRequested()` check inside its equivalent `while(!isFrameReady())` loop **and** a post-loop early-return guard (see `LoadScreen.cpp:1101–1112`). The `SinglePlayerLoadScreen` min-spec path in the `#if RTS_GENERALS` block has neither: the loop spins until the last frame finishes decoding with no way to interrupt it, and `frameDecompress()`/`frameRender()` are always called unconditionally afterward.
If the user closes the window (or presses Alt+F4) while this loop is running, the shutdown is silently deferred until the frame decode completes. More importantly, if the loop were ever made interruptible, skipping the ready-guard would pass a non-ready frame to `frameDecompress()` — the same class of bug the `ChallengeLoadScreen` fix was specifically addressing.
Suggested fix to match the pattern already applied to `ChallengeLoadScreen`:
```suggestion
while(!m_videoStream->isFrameReady())
{
if (GameClient::isMovieAbortRequested())
{
break;
}
Sleep(1);
}
if (!m_videoStream->isFrameReady())
{
return;
}
m_videoStream->frameDecompress();
m_videoStream->frameRender(m_videoBuffer);
```
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/GameEngine/Include/GameLogic/GameLogic.h
Line: 155
Comment:
**Misleading parameter rename — `saveGame` reads as "save the game"**
The declaration was changed from `Bool loadSaveGame` to `Bool saveGame`. `saveGame` is ambiguous: a reader will naturally parse it as "should I save the game?" rather than "am I loading from a save?". The implementation in `GameLogic.cpp` still uses `loadingSaveGame`, which is clear. Keeping the declaration consistent with the implementation avoids the confusion:
```suggestion
void startNewGame( Bool loadSaveGame );
```
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/GameEngine/Source/GameClient/GameClient.cpp
Line: 799
Comment:
**Trailing whitespace on blank line**
There is a stray tab/space on the blank line between `propagateMessages()` and the `if` check. The same trailing whitespace exists in the mirror implementation at `GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp` at the equivalent line.
```suggestion
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fixes frameDecompres..."
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
|
I noticed an issue that should probably be addressed. If you launch the game and immediately close it (from the taskbar), the game crashes because Call stack: Maybe the way to fix this is to add a condition to the The |
|
I've attempted to replicate the crash by repeatedly launching the game and closing it from the taskbar, using a script that increments the delay between launch and close by 10ms each iteration, I wasn't able to trigger it. Included the if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) |
Did you try with windowed mode? I'm not sure whether it works with full screen mode. I also used |
Fix silent no-op during loading screen when triggered via MSG_META_DEMO_INSTANT_QUIT (Alt+F4). Allow sending disconnect message depending on game type loaded. Differentiate between Alt+F4 and Window X button. Skip confirmation on Alt+F4 but keep disconnect messages if in multiplayer. Add if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) check. Fix game crash when closing immediately from taskbar (checking ThePlayerList initialization).
moved trystartnewgame to private
fixed MSG_SELF_DESTRUCT sent for single-player loading abort
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
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.