@@ -33,38 +33,14 @@ public final class TrackableSerializer {
3333 static final byte TYPE_CARD_VIEW = 0 ;
3434 static final byte TYPE_PLAYER_VIEW = 1 ;
3535
36- /**
37- * Lightweight serializable marker that replaces a TrackableObject reference.
38- */
39- static final class IdRef implements Serializable {
40- private static final long serialVersionUID = 1L ;
41- final byte typeTag ;
42- final int id ;
43-
44- IdRef (byte typeTag , int id ) {
45- this .typeTag = typeTag ;
46- this .id = id ;
47- }
36+ /** Marker for tracker-stable refs (top-level protocol method args, and PlayerView in events). */
37+ record IdRef (byte typeTag , int id ) implements Serializable {
38+ @ Serial private static final long serialVersionUID = 1L ;
4839 }
4940
50- /**
51- * Marker for a stale CardView reference — the object holds a previous
52- * incarnation of a card (same ID, different Java object) that has since
53- * been replaced in the tracker by a zone-change copy. Carries the
54- * image key and name so the decoder can construct a detached CardView
55- * with the correct display data.
56- */
57- static final class StaleCardRef implements Serializable {
58- private static final long serialVersionUID = 1L ;
59- final int id ;
60- final String imageKey ;
61- final String name ;
62-
63- StaleCardRef (int id , String imageKey , String name ) {
64- this .id = id ;
65- this .imageKey = imageKey ;
66- this .name = name ;
67- }
41+ /** CardView ref inside a wrapped event. {@code preserveSnapshot=true} forces fallback even if the tracker has the id. */
42+ record EventCardRef (int id , String name , String imageKey , boolean preserveSnapshot ) implements Serializable {
43+ @ Serial private static final long serialVersionUID = 1L ;
6844 }
6945
7046 static byte typeTagFor (TrackableObject obj ) {
@@ -83,59 +59,66 @@ static TrackableType<?> trackableTypeFor(byte typeTag) {
8359
8460 /**
8561 * Replaces TrackableObject references with {@link IdRef} markers, or
86- * {@link StaleCardRef} markers for CardViews whose tracker entry has
87- * been replaced by a zone-change copy. When {@code tracker} is null,
88- * stale detection is skipped (used by the client encoder, which has
89- * no game-state awareness).
62+ * {@link EventCardRef} markers for CardViews inside wrapped events
63+ * ({@code eventMode = true}). When the tracker holds a different object
64+ * for the CardView's id (zone-change copy), {@code preserveSnapshot} is
65+ * set so the receiver decodes a detached CardView from the carried name
66+ * and image key. When {@code tracker} is null, the snapshot check is
67+ * skipped (used by the client encoder, which has no game-state awareness).
9068 */
91- static Object replace (Object obj , Tracker tracker ) {
69+ static Object replace (Object obj , Tracker tracker , boolean eventMode ) {
9270 if (obj instanceof TrackableObject trackable ) {
9371 byte tag = typeTagFor (trackable );
94- if (tag >= 0 ) {
95- if (tracker != null ) {
96- TrackableType <?> type = trackableTypeFor (tag );
97- if (type != null ) {
98- Object tracked = tracker .getObj (type , trackable .getId ());
99- if (tracked != trackable && tag == TYPE_CARD_VIEW && tracked != null ) {
100- // Stale reference: previous incarnation of this card
101- CardView cv = (CardView ) trackable ;
102- String imgKey = cv .getCurrentState () != null
103- ? cv .getCurrentState ().getImageKey (null ) : null ;
104- return new StaleCardRef (cv .getId (), imgKey , cv .getName ());
105- }
72+ if (tag < 0 ) return obj ;
73+
74+ if (!eventMode || tag == TYPE_PLAYER_VIEW ) {
75+ return new IdRef (tag , trackable .getId ());
76+ }
77+
78+ boolean preserveSnapshot = false ;
79+ if (tracker != null ) {
80+ TrackableType <?> type = trackableTypeFor (tag );
81+ if (type != null ) {
82+ Object tracked = tracker .getObj (type , trackable .getId ());
83+ if (tracked != null && tracked != trackable ) {
84+ preserveSnapshot = true ;
10685 }
10786 }
108- return new IdRef (tag , trackable .getId ());
10987 }
88+ CardView cv = (CardView ) trackable ;
89+ String imgKey = cv .getCurrentState () != null
90+ ? cv .getCurrentState ().getImageKey (null ) : null ;
91+ return new EventCardRef (trackable .getId (), cv .getName (), imgKey , preserveSnapshot );
11092 }
11193 return obj ;
11294 }
11395
11496 /**
115- * Resolves {@link IdRef} and {@link StaleCardRef } markers back to
97+ * Resolves {@link IdRef} and {@link EventCardRef } markers back to
11698 * TrackableObjects from the given Tracker.
11799 */
118100 static Object resolve (Object obj , Tracker tracker ) {
119- if (obj instanceof StaleCardRef ref ) {
120- // Create a detached CardView with the correct image key.
121- // Not registered in the tracker — used only for display
122- // (game log thumbnail) so it won't affect live game state.
123- CardView detached = new CardView (ref .id , tracker );
124- if (ref .name != null ) {
125- detached .set (TrackableProperty .Name , ref .name );
126- detached .getCurrentState ().set (TrackableProperty .Name , ref .name );
101+ if (obj instanceof EventCardRef ref ) {
102+ if (!ref .preserveSnapshot ()) {
103+ CardView fromTracker = tracker .getObj (TrackableTypes .CardViewType , ref .id ());
104+ if (fromTracker != null ) return fromTracker ;
105+ }
106+ CardView detached = new CardView (ref .id (), tracker );
107+ if (ref .name () != null ) {
108+ detached .set (TrackableProperty .Name , ref .name ());
109+ detached .getCurrentState ().set (TrackableProperty .Name , ref .name ());
127110 }
128- if (ref .imageKey != null ) {
129- detached .getCurrentState ().set (TrackableProperty .ImageKey , ref .imageKey );
111+ if (ref .imageKey () != null ) {
112+ detached .getCurrentState ().set (TrackableProperty .ImageKey , ref .imageKey () );
130113 }
131114 return detached ;
132115 }
133116 if (obj instanceof IdRef ref ) {
134- TrackableType <?> type = trackableTypeFor (ref .typeTag );
117+ TrackableType <?> type = trackableTypeFor (ref .typeTag () );
135118 if (type != null ) {
136- Object resolved = tracker .getObj (type , ref .id );
119+ Object resolved = tracker .getObj (type , ref .id () );
137120 if (resolved == null ) {
138- netLog .warn ("Could not resolve IdRef(tag={}, id={}) from Tracker" , ref .typeTag , ref .id );
121+ netLog .warn ("Could not resolve IdRef(tag={}, id={}) from Tracker" , ref .typeTag () , ref .id () );
139122 }
140123 return resolved ;
141124 }
@@ -152,7 +135,7 @@ public static int measureSize(Object obj, Tracker tracker) {
152135 try {
153136 ByteArrayOutputStream baos = new ByteArrayOutputStream ();
154137 LZ4BlockOutputStream lz4Out = new LZ4BlockOutputStream (baos );
155- ObjectOutputStream oos = tracker == null ? new ObjectOutputStream (lz4Out ) : new ReplacingOutputStream (lz4Out , tracker );
138+ ObjectOutputStream oos = tracker == null ? new ObjectOutputStream (lz4Out ) : new ReplacingOutputStream (lz4Out , tracker , false );
156139 oos .writeObject (obj );
157140 oos .close ();
158141 return baos .size ();
@@ -163,7 +146,7 @@ public static int measureSize(Object obj, Tracker tracker) {
163146
164147 /**
165148 * Serializable wrapper for a GameEvent whose TrackableObject references
166- * have been replaced with IdRef/StaleCardRef markers. Stored in
149+ * have been replaced with IdRef/EventCardRef markers. Stored in
167150 * DeltaPacket.events so events travel as compact byte arrays rather than
168151 * full object graphs. Unwrapped after delta state is applied, when the
169152 * client tracker is populated.
@@ -183,7 +166,7 @@ public static List<Object> wrapEvents(List<GameEvent> events, Tracker tracker) {
183166 for (GameEvent event : events ) {
184167 try {
185168 ByteArrayOutputStream baos = new ByteArrayOutputStream (256 );
186- try (ReplacingOutputStream out = new ReplacingOutputStream (baos , tracker )) {
169+ try (ReplacingOutputStream out = new ReplacingOutputStream (baos , tracker , true )) {
187170 out .writeObject (event );
188171 }
189172 wrapped .add (new WrappedEvent (baos .toByteArray ()));
@@ -219,16 +202,18 @@ public static List<GameEvent> unwrapEvents(List<Object> items, Tracker tracker)
219202
220203 static class ReplacingOutputStream extends ObjectOutputStream {
221204 private final Tracker tracker ;
205+ private final boolean eventMode ;
222206
223- ReplacingOutputStream (OutputStream out , Tracker tracker ) throws IOException {
207+ ReplacingOutputStream (OutputStream out , Tracker tracker , boolean eventMode ) throws IOException {
224208 super (out );
225209 this .tracker = tracker ;
210+ this .eventMode = eventMode ;
226211 enableReplaceObject (true );
227212 }
228213
229214 @ Override
230215 protected Object replaceObject (Object obj ) {
231- return replace (obj , tracker );
216+ return replace (obj , tracker , eventMode );
232217 }
233218 }
234219
0 commit comments