@@ -206,6 +206,7 @@ export interface GraphAddOptions {
206206}
207207
208208const nodesAdoptingLayout = new WeakSet < LGraphNode > ( )
209+ const rerouteDataAdoptingLayout = new WeakSet < object > ( )
209210
210211export interface LGraphExtra extends Dictionary < unknown > {
211212 reroutes ?: SerialisableReroute [ ]
@@ -503,12 +504,11 @@ export class LGraph
503504 }
504505
505506 private clearWithResult ( ) : LayoutOperationResult {
506- this . stop ( )
507- this . status = LGraph . STATUS_STOPPED
508-
509507 const graphId = this . id
510508 const layoutResult = unregisterAllGraphLayout ( this )
511509 if ( layoutResult === 'rejected' ) return 'rejected'
510+ this . stop ( )
511+ this . status = LGraph . STATUS_STOPPED
512512 if ( this . isRootGraph && graphId !== zeroUuid ) {
513513 usePreviewExposureStore ( ) . clearGraph ( graphId )
514514 useWidgetValueStore ( ) . clearGraph ( graphId )
@@ -1194,8 +1194,7 @@ export class LGraph
11941194 restoreNodeIdentity ( )
11951195 throw error
11961196 }
1197- // A no-op means the entry already exists; re-registering is not an error.
1198- if ( registrationResult === 'rejected' ) {
1197+ if ( registrationResult !== 'applied' ) {
11991198 restoreNodeIdentity ( )
12001199 return
12011200 }
@@ -1293,6 +1292,16 @@ export class LGraph
12931292 // sure? - almost sure is wrong
12941293 try {
12951294 if ( layoutDetach . result === 'rejected' ) return
1295+ const releasedSubgraphs = node . isSubgraphNode ( )
1296+ ? findReleasableSubgraphs ( this . rootGraph , node )
1297+ : [ ]
1298+ for ( const subgraph of releasedSubgraphs ) {
1299+ layoutDetach . includeGraph ( subgraph )
1300+ if ( unregisterAllGraphLayout ( subgraph ) === 'rejected' ) {
1301+ layoutDetach . restore ( undefined )
1302+ return
1303+ }
1304+ }
12961305 this . beforeChange ( )
12971306
12981307 this . events . dispatch ( 'node:before-removed' , { node } )
@@ -1320,14 +1329,8 @@ export class LGraph
13201329 }
13211330 }
13221331
1323- const initiallyReleasedSubgraphs = node . isSubgraphNode ( )
1324- ? findReleasableSubgraphs ( this . rootGraph , node )
1325- : [ ]
13261332 if ( node . isSubgraphNode ( ) ) {
1327- for ( const subgraph of initiallyReleasedSubgraphs ) {
1328- layoutDetach . includeGraph ( subgraph )
1329- }
1330- for ( const subgraph of initiallyReleasedSubgraphs ) {
1333+ for ( const subgraph of releasedSubgraphs ) {
13311334 visitGraphNodes ( subgraph , fireNodeRemovalLifecycle )
13321335 }
13331336 }
@@ -1348,18 +1351,6 @@ export class LGraph
13481351 }
13491352 }
13501353
1351- const releasedSubgraphs = node . isSubgraphNode ( )
1352- ? findReleasableSubgraphs ( this . rootGraph , node ) . filter ( ( subgraph ) =>
1353- initiallyReleasedSubgraphs . includes ( subgraph )
1354- )
1355- : [ ]
1356- for ( const subgraph of releasedSubgraphs ) {
1357- if ( unregisterAllGraphLayout ( subgraph ) === 'rejected' ) {
1358- node . graph = this
1359- layoutDetach . restore ( undefined )
1360- return
1361- }
1362- }
13631354 for ( const subgraph of releasedSubgraphs ) {
13641355 unregisterAllLinkTopologies ( subgraph )
13651356 unregisterAllRerouteChains ( subgraph )
@@ -1715,7 +1706,7 @@ export class LGraph
17151706 * populating {@link reroutes}; routing every add through here keeps the
17161707 * store from silently desyncing.
17171708 */
1718- _addReroute ( reroute : Reroute ) : void {
1709+ _addReroute ( reroute : Reroute , adoptExisting = false ) : void {
17191710 if ( reroute . network . deref ( ) !== this ) {
17201711 throw new Error (
17211712 `Reroute ${ reroute . id } may only attach to its constructor graph`
@@ -1731,7 +1722,15 @@ export class LGraph
17311722 if ( existing && existing !== reroute ) {
17321723 throw new Error ( `Reroute ${ reroute . id } is already owned by this graph` )
17331724 }
1734- if ( existing ) return
1725+ if ( existing ) {
1726+ if ( adoptExisting ) {
1727+ const result = materializeRerouteLayout ( this , reroute )
1728+ if ( result !== 'applied' ) {
1729+ throw new Error ( `Reroute layout registration ${ result } ` )
1730+ }
1731+ }
1732+ return
1733+ }
17351734
17361735 const position = { x : reroute . pos [ 0 ] , y : reroute . pos [ 1 ] }
17371736 registerRerouteChain ( this , reroute )
@@ -1742,10 +1741,12 @@ export class LGraph
17421741 unregisterRerouteChain ( reroute )
17431742 throw error
17441743 }
1745- // A no-op means the entry already exists; re-registering is not an error.
1746- if ( registrationResult === 'rejected' ) {
1744+ if ( registrationResult === 'no-op' && adoptExisting ) {
1745+ registrationResult = materializeRerouteLayout ( this , reroute )
1746+ }
1747+ if ( registrationResult !== 'applied' ) {
17471748 unregisterRerouteChain ( reroute )
1748- return
1749+ throw new Error ( `Reroute layout registration ${ registrationResult } ` )
17491750 }
17501751 this . reroutesInternal . set ( reroute . id , reroute )
17511752 reroute . _attachedGraph = new WeakRef ( this )
@@ -1769,32 +1770,24 @@ export class LGraph
17691770 * Creates the object if it does not exist.
17701771 * @param serialisedReroute See {@link SerialisableReroute}
17711772 */
1772- setReroute ( {
1773- id,
1774- parentId,
1775- pos,
1776- floating
1777- } : OptionalProps < SerialisableReroute , 'id' > ) : Reroute {
1778- const originalLastRerouteId = this . state . lastRerouteId
1773+ setReroute (
1774+ serialisedReroute : OptionalProps < SerialisableReroute , 'id' >
1775+ ) : Reroute {
1776+ const { id, parentId, pos, floating } = serialisedReroute
17791777 const rerouteId =
17801778 id === undefined
17811779 ? toRerouteId ( Number ( this . state . lastRerouteId ) + 1 )
17821780 : toRerouteId ( id )
1783- if ( rerouteId > this . state . lastRerouteId ) {
1784- this . state . lastRerouteId = rerouteId
1785- }
17861781
17871782 const existingReroute = this . reroutes . get ( rerouteId )
17881783 const reroute = existingReroute ?? new Reroute ( rerouteId , this , pos )
1784+ this . _addReroute ( reroute , rerouteDataAdoptingLayout . has ( serialisedReroute ) )
1785+ if ( pos && existingReroute ) reroute . pos = pos
17891786 reroute . parentId =
17901787 parentId === undefined ? undefined : toRerouteId ( parentId )
1791- if ( pos && existingReroute ) reroute . pos = pos
17921788 reroute . floating = floating
1793- try {
1794- this . _addReroute ( reroute )
1795- } catch ( error ) {
1796- this . state . lastRerouteId = originalLastRerouteId
1797- throw error
1789+ if ( rerouteId > this . state . lastRerouteId ) {
1790+ this . state . lastRerouteId = rerouteId
17981791 }
17991792 return reroute
18001793 }
@@ -2727,29 +2720,58 @@ export class LGraph
27272720 const targetGraphId = this . isRootGraph
27282721 ? ( serializedRootId ?? ( keepsOldState ? this . id : undefined ) )
27292722 : this . rootGraph . id
2730- const graphs = [
2731- {
2732- graph : this ,
2733- reroutes : data . version === 0.4 ? data . extra ?. reroutes : data . reroutes
2734- } ,
2735- ...( data . definitions ?. subgraphs ?? [ ] ) . map ( ( subgraph ) => ( {
2736- graph : this . subgraphs . get ( subgraph . id ) ,
2737- reroutes : subgraph . reroutes
2738- } ) )
2723+ const serializedGraphs = [
2724+ data . version === 0.4 ? data . extra ?. reroutes : data . reroutes ,
2725+ ...( data . definitions ?. subgraphs ?? [ ] ) . map (
2726+ ( subgraph ) => subgraph . reroutes
2727+ )
27392728 ]
2740- const incomingRerouteIds = new Set < RerouteId > ( )
2741- for ( const { reroutes } of graphs ) {
2729+ for ( const reroutes of serializedGraphs ) {
2730+ const graphRerouteIds = new Set < RerouteId > ( )
27422731 for ( const { id } of reroutes ?? [ ] ) {
27432732 const rerouteId = toRerouteId ( id )
2744- if ( incomingRerouteIds . has ( rerouteId ) ) {
2733+ if ( graphRerouteIds . has ( rerouteId ) ) {
27452734 throw new Error (
27462735 `Reroute ${ rerouteId } appears more than once in the configuration`
27472736 )
27482737 }
2749- incomingRerouteIds . add ( rerouteId )
2738+ graphRerouteIds . add ( rerouteId )
27502739 }
27512740 }
27522741
2742+ const temporaryState = { ...this . state }
2743+ let subgraphs = data . definitions ?. subgraphs
2744+ if ( subgraphs ) {
2745+ const clonedSubgraphs = structuredClone ( subgraphs )
2746+ const reservedRerouteIds = keepsOldState
2747+ ? collectReservedRerouteIds ( this )
2748+ : new Set < RerouteId > ( )
2749+ for ( const { id } of ( data . version === 0.4
2750+ ? data . extra ?. reroutes
2751+ : data . reroutes ) ?? [ ] ) {
2752+ reservedRerouteIds . add ( id )
2753+ }
2754+ deduplicateSubgraphRerouteIds (
2755+ clonedSubgraphs ,
2756+ reservedRerouteIds ,
2757+ temporaryState
2758+ )
2759+ data = {
2760+ ...data ,
2761+ definitions : { ...data . definitions , subgraphs : clonedSubgraphs }
2762+ }
2763+ subgraphs = clonedSubgraphs
2764+ }
2765+ const graphs = [
2766+ {
2767+ graph : this ,
2768+ reroutes : data . version === 0.4 ? data . extra ?. reroutes : data . reroutes
2769+ } ,
2770+ ...( data . definitions ?. subgraphs ?? [ ] ) . map ( ( subgraph ) => ( {
2771+ graph : this . subgraphs . get ( subgraph . id ) ,
2772+ reroutes : subgraph . reroutes
2773+ } ) )
2774+ ]
27532775 if ( targetGraphId && targetGraphId !== zeroUuid ) {
27542776 const store = useRerouteStore ( )
27552777 const replaceableOwners = keepsOldState
@@ -2787,6 +2809,7 @@ export class LGraph
27872809 ? this . clearWithResult ( )
27882810 : unregisterAllGraphLayout ( this )
27892811 if ( layoutResult === 'rejected' ) return
2812+ this . state . lastRerouteId = temporaryState . lastRerouteId
27902813
27912814 this . _configureBase ( data )
27922815
@@ -2850,8 +2873,12 @@ export class LGraph
28502873 // Reroutes
28512874 if ( Array . isArray ( reroutes ) ) {
28522875 for ( const rerouteData of reroutes ) {
2853- const reroute = this . setReroute ( rerouteData )
2854- materializeRerouteLayout ( this , reroute )
2876+ rerouteDataAdoptingLayout . add ( rerouteData )
2877+ try {
2878+ this . setReroute ( rerouteData )
2879+ } finally {
2880+ rerouteDataAdoptingLayout . delete ( rerouteData )
2881+ }
28552882 }
28562883 }
28572884
@@ -2868,7 +2895,6 @@ export class LGraph
28682895 // Subgraph definitions — deduplicate node IDs before configuring.
28692896 // deduplicateSubgraphNodeIds clones internally to avoid mutating
28702897 // the caller's data (e.g. reactive Pinia state).
2871- const subgraphs = data . definitions ?. subgraphs
28722898 let effectiveNodesData = nodesData
28732899 if ( subgraphs ) {
28742900 const reservedNodeIds = new Set < number > ( )
@@ -2901,11 +2927,6 @@ export class LGraph
29012927 collectReservedGroupIds ( this , data . groups ) ,
29022928 this . state
29032929 )
2904- deduplicateSubgraphRerouteIds (
2905- deduplicated . subgraphs ,
2906- collectReservedRerouteIds ( this ) ,
2907- this . state
2908- )
29092930 }
29102931
29112932 const finalSubgraphs = deduplicated ?. subgraphs ?? subgraphs
0 commit comments