Post-match lobby cleanup and network concede fixes#10410
Merged
tool4ever merged 5 commits intoCard-Forge:masterfrom Apr 17, 2026
Merged
Post-match lobby cleanup and network concede fixes#10410tool4ever merged 5 commits intoCard-Forge:masterfrom
tool4ever merged 5 commits intoCard-Forge:masterfrom
Conversation
When a match ends, the server now cleans up properly: nulls hostedMatch, resets slot ready states, removes forwarder observers from InputQueues, clears cached RemoteClientGuiGame instances, and broadcasts a fresh LobbyUpdateEvent so clients refresh their lobby UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Network concede was broken: the client's concede dialog never appeared (instanceof check excluded NetGameController), the game thread stayed blocked after concede (only the conceding player's input queue was released), and game-end events weren't flushed to remote clients. - AbstractGuiGame.concede(): include network controllers in concede check; don't send premature nextGameDecision for network games - PlayerControllerHuman.concede(): release all players' input latches after game ends, not just the conceding player's - HostedMatch: flush GameEventForwarder after game loop exits so remote clients receive GameEventGameFinished - GameProtocolHandler: null-guard getToInvoke() for messages arriving after cleanup, with null reply for non-void methods - GameServerHandler: null-guard getClient() for deregistered clients Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
reviewed
Apr 16, 2026
tool4ever
reviewed
Apr 16, 2026
…comment Fold the new observer-deletion loop into the existing per-controller cleanup loop in HostedMatch.endCurrentGame(). Each iteration now captures the forwarder, deletes observers, then calls shutdownForwarder — the ordering invariant is enforced by adjacency rather than loop split. Update the input-latch-release comment in PlayerControllerHuman.concede() to explain the actual reason the loop is needed: remote-client controllers on the server have no FControlGameEventHandler subscribed, so their input queues are not released by the normal event path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
reviewed
Apr 16, 2026
GameLobby.onMatchOver unconditionally cleared isReady on every slot, which broke LocalLobby: the human slot starts ready and has no UI to re-ready, so the second match failed with "Player X is not ready". Move the reset into ServerGameLobby.onMatchOver so network clients still re-ready between matches while local play is unaffected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7 tasks
tool4ever
approved these changes
Apr 17, 2026
MostCromulent
added a commit
to MostCromulent/forge
that referenced
this pull request
Apr 17, 2026
Master added an onMatchOver override on ServerGameLobby for post-match cleanup (PR <!-- -->Card-Forge#10410). Conflicted with this branch's event-lifecycle additions at the end of the class body. Resolved by keeping both: our createEvent/selectEventForMatch/getDraftHost block + master's onMatchOver override. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses Phase 0.5 of the network draft/sealed implementation plan — hardening existing network match lifecycle before adding new game modes. Also fixes several pre-existing network concede bugs observed during testing. See discussion at Card-Forge/forge#9861 (comment) and follow-up.
Issues addressed
No lobby cleanup after network match — the previous match's state lingered in the lobby after returning. Added
onMatchOvercallback to reset lobby state (null hostedMatch, un-ready slots, clear player GUIs, push lobby update to clients).Client concede dialog never appeared —
instanceof PlayerControllerHumanexcludedNetGameController, so the concede confirmation was silently skipped. Broadened the check to include network controllers.Client concede force-quit both players — client sent a premature
nextGameDecision(QUIT)before the server processed the concede, ending the match for both. Network games now let the server drive the game-end flow.Game thread blocked after concede — only the conceding player's input latch was released, but the game thread could be waiting on the other player's input queue. Now releases all players' input latches.
Remote client never saw win/lose screen — game-end events were buffered in
GameEventForwarderand never flushed. Added explicit flush after the game loop exits.NPEs from late-arriving messages — protocol messages arriving after cleanup caused
NullPointerExceptionon the server. Added null guards in the protocol handler with null reply for non-void methods.Why these changes are safe
NextGameDecision.QUIT— the same path that already callsendCurrentGame(). It adds cleanup after existing teardown, doesn't change teardown order.PlayerControllerHumanpath with the per-player outcome check. No behavior change for single-player or local multiplayer.isNetGame()guard). Local games still sendnextGameDecisionsynchronously as before.CountDownLatch.countDown()on an already-released latch is a no-op.getToInvoke()andgetClient()always return non-null.🤖 Generated with Claude Code