Add GameEventProxy to reduce network event serialization size#10018
Merged
tool4ever merged 9 commits intoCard-Forge:masterfrom Mar 9, 2026
Merged
Add GameEventProxy to reduce network event serialization size#10018tool4ever merged 9 commits intoCard-Forge:masterfrom
tool4ever merged 9 commits intoCard-Forge:masterfrom
Conversation
Wraps GameEvent objects with lightweight ID markers for CardView and PlayerView references, preventing Java serialization from expanding the full game state object graph in each event batch. Increases the network event flush interval from 50ms to 500ms to reduce batch count. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
reviewed
Mar 8, 2026
tool4ever
reviewed
Mar 8, 2026
Address PR review feedback: remove GameEventProxyTest (redundant with GameEventSerializationTest) and remove the two-arg constructor overload from GameEventForwarder, making 500ms the default flush interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
reviewed
Mar 8, 2026
21 tasks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bb11996 to
2802534
Compare
Previously events were only flushed when a new event arrived AND the interval had elapsed. Events buffered mid-action (e.g. land play) would stay pending until the next player action or priority change. Now a deferred flush is scheduled when events are buffered, guaranteeing delivery within the flush interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
reviewed
Mar 9, 2026
When GameEventProxy resolves IdRefs, it looks up CardViews from the client's Tracker. These CardViews were registered on the first setGameView and never updated — updateObjLookup skips existing entries. The stale CardViews retained their original zone (e.g. Library), causing canBeShownTo to return false and getImageKey to return the hidden-card token instead of the actual card art. Add refreshTrackerCardViews() to replace tracker entries with incoming server CardViews on each setGameView, so IdRef resolution always gets current zone and state data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add NetGuiGame.shutdownForwarder() and call it from HostedMatch.endCurrentGame() to clean up the scheduled executor when the game ends. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tool4ever
approved these changes
Mar 9, 2026
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.
@tool4ever latest salvo in our war to win the GameEvent refactor.
Summary
GameEventProxy, which wrapsGameEventobjects by replacingCardViewandPlayerViewreferences with lightweight ID markers before network serialization, preventing Java serialization from expanding the full game state object graph in each event batchAdds a unit test that discovers allGameEventrecord types via reflection and verifies each can be serialized by the proxyHow it works
GameEventProxyuses Java'sObjectOutputStream.replaceObject()/ObjectInputStream.resolveObject()hooks to automatically substituteTrackableObjectreferences withIdRef(typeTag, id)markers during local byte-array serialization. This handles all nesting (records, collections, maps) automatically — no per-event-type handling needed.Server side: In
NetGuiGame.handleGameEvents(), eachGameEventis serialized into a byte array with CardView/PlayerView references replaced by IdRef markers. The resultingGameEventProxyis just abyte[]wrapper — Netty encodes it without traversing the game state object graph.Client side: In
GameClientHandler.beforeCall(handleGameEvents), proxy byte arrays are deserialized with IdRef markers resolved back toTrackableObjectinstances from the client'sTracker. To ensure all objects are available for resolution,updateObjLookup()is called synchronously on the IO thread whensetGameViewarrives (sincecopyChangedProps()runs asynchronously on the EDT and may not have completed yet).Why only CardView and PlayerView?
Only these two types are replaced with ID markers. Other
TrackableObjecttypes serialize normally (they are small and don't cause graph expansion):GameViewreflects current state, so the StackItemView may no longer be present forupdateObjLookup()to register.GameView's property graph. NoTrackablePropertyusesSpellAbilityViewType, soupdateObjLookup()on the incomingGameViewnever discovers these objects.GameEventrecord.CardView; not independently referenced in events.If wrapping fails for any individual event, the original event is sent as a fallback.
## TestGameEventProxyTestuses Guava'sClassPathto discover allGameEventrecord classes inforge.game.event, constructs each with default values, and verifiesGameEventProxy.wrap()succeeds. This catches non-serializable fields in new or modified events at build time rather than at runtime during network play.Local testing
Verified with manual network games (server + client on localhost):
handleGameEventsmessages no longer appear in the encoder size log (below the 20KB reporting threshold), confirming the proxy is effectivesetGameViewmessages (~20KB compressed) appear in the encoder log — unchanged from before🤖 Generated with Claude Code