@@ -41,7 +41,7 @@ namespace gdjs {
4141 objA : gdjs . RuntimeObject
4242 ) : Map < string , gdjs . RuntimeObject [ ] > {
4343 if ( ! this . _links . has ( objA . id ) ) {
44- this . _links . set ( objA . id , new IterableLinkedObjects ( ) ) ;
44+ this . _links . set ( objA . id , new IterableLinkedObjects ( objA ) ) ;
4545 }
4646 return this . _links . get ( objA . id ) ! . linkedObjectMap ;
4747 }
@@ -55,7 +55,7 @@ namespace gdjs {
5555 objA : gdjs . RuntimeObject
5656 ) : Iterable < gdjs . RuntimeObject > {
5757 if ( ! this . _links . has ( objA . id ) ) {
58- this . _links . set ( objA . id , new IterableLinkedObjects ( ) ) ;
58+ this . _links . set ( objA . id , new IterableLinkedObjects ( objA ) ) ;
5959 }
6060 return this . _links . get ( objA . id ) ! ;
6161 }
@@ -159,15 +159,83 @@ namespace gdjs {
159159 }
160160 }
161161 }
162+
163+ clearAllLinks ( ) {
164+ this . _links . clear ( ) ;
165+ }
166+
167+ /**
168+ * Serialize all links between objects that have a networkId,
169+ * so they can be persisted and restored later.
170+ * Each link is stored once as a pair of networkIds.
171+ */
172+ getNetworkSyncData ( ) : Array < [ string , string ] > {
173+ const linkedObjects : Array < [ string , string ] > = [ ] ;
174+ const serializedLinks = new Set < string > ( ) ;
175+
176+ for ( const iterableLinkedObjects of this . _links . values ( ) ) {
177+ const objectA = iterableLinkedObjects . ownerObject ;
178+ if ( ! objectA || ! objectA . networkId ) {
179+ continue ;
180+ }
181+
182+ for ( const objectB of iterableLinkedObjects ) {
183+ if ( ! objectB . networkId ) continue ;
184+ const pairKey =
185+ objectA . networkId < objectB . networkId
186+ ? `${ objectA . networkId } |${ objectB . networkId } `
187+ : `${ objectB . networkId } |${ objectA . networkId } ` ;
188+ if ( serializedLinks . has ( pairKey ) ) continue ;
189+ serializedLinks . add ( pairKey ) ;
190+
191+ linkedObjects . push ( [ objectA . networkId , objectB . networkId ] ) ;
192+ }
193+ }
194+
195+ return linkedObjects ;
196+ }
197+
198+ /**
199+ * Restore links from serialized data. Objects must already exist
200+ * in the scene with their networkId set.
201+ *
202+ * Links for objects managed by the save state should be cleared
203+ * before calling this method, so that stale links are removed.
204+ * This method only adds the saved links back.
205+ */
206+ updateFromNetworkSyncData (
207+ linksNetworkSyncData : Array < [ string , string ] > ,
208+ runtimeScene : gdjs . RuntimeScene
209+ ) : void {
210+ if ( ! linksNetworkSyncData ) return ;
211+
212+ // Build a map from networkId to object instance for quick lookup.
213+ const objectsByNetworkId = new Map < string , gdjs . RuntimeObject > ( ) ;
214+ for ( const object of runtimeScene . getAdhocListOfAllInstances ( ) ) {
215+ if ( object . networkId ) {
216+ objectsByNetworkId . set ( object . networkId , object ) ;
217+ }
218+ }
219+
220+ for ( const [ networkIdA , networkIdB ] of linksNetworkSyncData ) {
221+ const objectA = objectsByNetworkId . get ( networkIdA ) ;
222+ const objectB = objectsByNetworkId . get ( networkIdB ) ;
223+ if ( ! objectA || ! objectB ) continue ;
224+
225+ this . linkObjects ( objectA , objectB ) ;
226+ }
227+ }
162228 }
163229
164230 class IterableLinkedObjects implements Iterable < gdjs . RuntimeObject > {
231+ ownerObject : gdjs . RuntimeObject ;
165232 linkedObjectMap : Map < string , gdjs . RuntimeObject [ ] > ;
166233 static emptyItr : Iterator < gdjs . RuntimeObject > = {
167234 next : ( ) => ( { value : undefined , done : true } ) ,
168235 } ;
169236
170- constructor ( ) {
237+ constructor ( ownerObject : gdjs . RuntimeObject ) {
238+ this . ownerObject = ownerObject ;
171239 this . linkedObjectMap = new Map < string , gdjs . RuntimeObject [ ] > ( ) ;
172240 }
173241
@@ -201,6 +269,36 @@ namespace gdjs {
201269 }
202270 ) ;
203271
272+ gdjs . registerRuntimeSceneGetSyncDataCallback (
273+ function ( runtimeScene , currentLayoutSyncData , syncOptions ) {
274+ if ( ! syncOptions . syncLinkedObjects ) return ;
275+
276+ currentLayoutSyncData . linkedObjects =
277+ LinksManager . getManager ( runtimeScene ) . getNetworkSyncData ( ) ;
278+ }
279+ ) ;
280+
281+ gdjs . registerRuntimeSceneUpdateFromSyncDataCallback (
282+ function ( runtimeScene , receivedSyncData , _syncOptions ) {
283+ if ( ! receivedSyncData . linkedObjects ) return ;
284+
285+ const linksManager = LinksManager . getManager ( runtimeScene ) ;
286+
287+ // Clear links only for objects with a networkId (managed by save state).
288+ // DoNotSave objects don't have networkIds, so their links are preserved.
289+ for ( const object of runtimeScene . getAdhocListOfAllInstances ( ) ) {
290+ if ( object . networkId ) {
291+ linksManager . removeAllLinksOf ( object ) ;
292+ }
293+ }
294+
295+ linksManager . updateFromNetworkSyncData (
296+ receivedSyncData . linkedObjects ,
297+ runtimeScene
298+ ) ;
299+ }
300+ ) ;
301+
204302 export const linkObjects = function (
205303 instanceContainer : gdjs . RuntimeInstanceContainer ,
206304 objA : gdjs . RuntimeObject | null ,
0 commit comments