diff --git a/src/lib/litegraph/src/LGraph.test.ts b/src/lib/litegraph/src/LGraph.test.ts index bbb0b7852f..2d340dc922 100644 --- a/src/lib/litegraph/src/LGraph.test.ts +++ b/src/lib/litegraph/src/LGraph.test.ts @@ -399,8 +399,6 @@ describe('Graph Clearing and Callbacks', () => { }) test('clear() removes graph-scoped preview and widget-value state', () => { - setActivePinia(createTestingPinia({ stubActions: false })) - const graph = new LGraph() const graphId = 'graph-clear-cleanup' as UUID graph.id = graphId @@ -529,10 +527,6 @@ describe('node:before-removed event', () => { }) describe('Subgraph Definition Garbage Collection', () => { - beforeEach(() => { - setActivePinia(createTestingPinia({ stubActions: false })) - }) - function createSubgraphWithNodes(rootGraph: LGraph, nodeCount: number) { const subgraph = rootGraph.createSubgraph(createTestSubgraphData()) @@ -891,8 +885,6 @@ describe('ensureGlobalIdUniqueness', () => { }) describe('_removeDuplicateLinks', () => { - beforeEach(() => setActivePinia(createTestingPinia({ stubActions: false }))) - class TestNode extends LGraphNode { constructor(title?: string) { super(title ?? 'TestNode') @@ -1207,7 +1199,6 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => { const SHARED_NODE_IDS = [3, 8, 37] beforeEach(() => { - setActivePinia(createTestingPinia({ stubActions: false })) LiteGraph.registerNodeType('dummy', DummyNode) }) diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index 9f2c98741a..9796ba42f4 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -43,6 +43,7 @@ import { MapProxyHandler } from './MapProxyHandler' import { registerRerouteChain, Reroute, + unregisterAllRerouteChains, unregisterRerouteChain } from './Reroute' import type { RerouteId } from './Reroute' @@ -415,9 +416,7 @@ export class LGraph // Subgraphs and unconfigured (zero-uuid) graphs share their store // bucket with other graphs, so unregister each link individually. unregisterAllLinkTopologies(this) - for (const reroute of this.reroutes.values()) { - unregisterRerouteChain(reroute) - } + unregisterAllRerouteChains(this) } this.id = zeroUuid @@ -1142,9 +1141,7 @@ export class LGraph if (!hasRemainingReferences) { forEachNode(node.subgraph, fireNodeRemovalLifecycle) unregisterAllLinkTopologies(node.subgraph) - for (const reroute of node.subgraph.reroutes.values()) { - unregisterRerouteChain(reroute) - } + unregisterAllRerouteChains(node.subgraph) this.rootGraph.subgraphs.delete(node.subgraph.id) } } @@ -1587,9 +1584,10 @@ export class LGraph } const reroute = this.reroutes.get(rerouteId) ?? new Reroute(rerouteId, this) - const typedParentId = + reroute.parentId = parentId === undefined ? undefined : toRerouteId(parentId) - reroute.update(typedParentId, pos, floating) + if (pos) reroute.pos = pos + reroute.floating = floating this._addReroute(reroute) return reroute } @@ -1608,10 +1606,15 @@ export class LGraph } const rerouteId = toRerouteId(Number(this.state.lastRerouteId) + 1) this.state.lastRerouteId = rerouteId - const linkIds = - before instanceof Reroute ? [...before.linkIds] : [before.id] - const floatingLinkIds = - before instanceof Reroute ? [...before.floatingLinkIds] : [before.id] + const chainLinks = + before instanceof Reroute + ? [ + ...[...before.linkIds].map((id) => this._links.get(id)), + ...[...before.floatingLinkIds].map((id) => + this.floatingLinks.get(id) + ) + ] + : [before] const reroute = new Reroute(rerouteId, this, pos, before.parentId) this._addReroute(reroute) @@ -1621,22 +1624,11 @@ export class LGraph rerouteId, { x: pos[0], y: pos[1] }, before.parentId, - linkIds + before instanceof Reroute ? [...before.linkIds] : [before.id] ) - for (const linkId of linkIds) { - const link = this._links.get(linkId) - if (!link) continue - if (link.parentId === before.parentId) link.parentId = rerouteId - - const reroutes = LLink.getReroutes(this, link) - for (const x of reroutes.filter((x) => x.parentId === before.parentId)) { - x.parentId = rerouteId - } - } - - for (const linkId of floatingLinkIds) { - const link = this.floatingLinks.get(linkId) + // Splice the new reroute into every chain that contained `before` + for (const link of chainLinks) { if (!link) continue if (link.parentId === before.parentId) link.parentId = rerouteId diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 70e7c38941..7c8a9169eb 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -32,6 +32,7 @@ import { LGraphButton } from './LGraphButton' import type { LGraphButtonOptions } from './LGraphButton' import { LGraphCanvas } from './LGraphCanvas' import { LLink } from './LLink' +import { anchorRerouteChain } from './Reroute' import type { Reroute, RerouteId } from './Reroute' import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots' import type { IDrawBoundingOptions } from './draw' @@ -2987,23 +2988,7 @@ export class LGraphNode }) } - // Reroutes - const reroutes = LLink.getReroutes(graph, link) - for (const reroute of reroutes) { - if (reroute.floating) reroute.floating = undefined - reroute._dragging = undefined - } - - // If this is the terminus of a floating link, remove it - const lastReroute = reroutes.at(-1) - if (lastReroute) { - for (const linkId of lastReroute.floatingLinkIds) { - const link = graph.floatingLinks.get(linkId) - if (link?.parentId === lastReroute.id) { - graph.removeFloatingLink(link) - } - } - } + anchorRerouteChain(graph, link) graph.incrementVersion() // link has been created now, so its updated diff --git a/src/lib/litegraph/src/Reroute.ts b/src/lib/litegraph/src/Reroute.ts index 8c54b1555b..cb7e076860 100644 --- a/src/lib/litegraph/src/Reroute.ts +++ b/src/lib/litegraph/src/Reroute.ts @@ -242,23 +242,6 @@ export class Reroute if (pos) this.pos = pos } - /** - * Applies a new parentId to the reroute, and optionally a new position. - * Primarily used for deserialisation. - * @param parentId The ID of the reroute prior to this reroute, or - * `undefined` if it is the first reroute connected to a nodes output - * @param pos The position of this reroute - */ - update( - parentId: RerouteId | undefined, - pos?: Point, - floating?: FloatingRerouteSlot - ): void { - this.parentId = parentId - if (pos) this.pos = pos - this.floating = floating - } - /** * Retrieves an ordered array of all reroutes from the node output. * @param visited Internal. A set of reroutes that this function @@ -806,6 +789,31 @@ function getDirection(fromPos: Point, toPos: Point) { return Math.atan2(toPos[1] - fromPos[1], toPos[0] - fromPos[0]) } +/** + * Marks a link's reroute chain as no longer floating: clears each reroute's + * floating marker and drag state, and removes any floating link that + * terminates at the chain's last reroute. Call when a real link connects + * through the chain. + * @param network The network containing the chain + * @param link The link whose chain was just connected + */ +export function anchorRerouteChain(network: LinkNetwork, link: LLink): void { + const reroutes = LLink.getReroutes(network, link) + for (const reroute of reroutes) { + reroute.floating = undefined + reroute._dragging = undefined + } + + const lastReroute = reroutes.at(-1) + if (!lastReroute) return + for (const linkId of lastReroute.floatingLinkIds) { + const floatingLink = network.floatingLinks.get(linkId) + if (floatingLink?.parentId === lastReroute.id) { + network.removeFloatingLink(floatingLink) + } + } +} + /** * Registers a reroute's chain state into {@link useRerouteStore} and adopts * the store's reactive proxy as {@link Reroute._chain}, so the store and the @@ -833,3 +841,15 @@ export function unregisterRerouteChain(reroute: Reroute): void { useRerouteStore().deleteReroute(reroute._graphId, reroute._chain) reroute._graphId = undefined } + +/** + * Unregisters every reroute a graph owns. Used when a graph's reroutes + * leave the store without a whole-bucket wipe: subgraph-definition removal, + * and clearing a graph that shares its bucket with other graphs. + * @param graph The graph whose reroutes should be unregistered + */ +export function unregisterAllRerouteChains( + graph: Pick +): void { + for (const reroute of graph.reroutes.values()) unregisterRerouteChain(reroute) +} diff --git a/src/lib/litegraph/src/subgraph/SubgraphInput.ts b/src/lib/litegraph/src/subgraph/SubgraphInput.ts index c95ed9552f..a3f7b3db02 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphInput.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphInput.ts @@ -1,6 +1,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { LLink } from '@/lib/litegraph/src/LLink' import { toLinkId } from '@/types/linkId' +import { anchorRerouteChain } from '@/lib/litegraph/src/Reroute' import type { RerouteId } from '@/lib/litegraph/src/Reroute' import { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget' import type { SubgraphInputEventMap } from '@/lib/litegraph/src/infrastructure/SubgraphInputEventMap' @@ -120,23 +121,7 @@ export class SubgraphInput extends SubgraphSlot { this.linkIds.push(link.id) slot.link = link.id - // Reroutes - const reroutes = LLink.getReroutes(subgraph, link) - for (const reroute of reroutes) { - reroute.floating = undefined - reroute._dragging = undefined - } - - // If this is the terminus of a floating link, remove it - const lastReroute = reroutes.at(-1) - if (lastReroute) { - for (const linkId of lastReroute.floatingLinkIds) { - const link = subgraph.floatingLinks.get(linkId) - if (link?.parentId === lastReroute.id) { - subgraph.removeFloatingLink(link) - } - } - } + anchorRerouteChain(subgraph, link) subgraph.incrementVersion() subgraph.trigger('node:slot-links:changed', { diff --git a/src/lib/litegraph/src/subgraph/SubgraphOutput.ts b/src/lib/litegraph/src/subgraph/SubgraphOutput.ts index 81f1022bd2..44a7ce0575 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphOutput.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphOutput.ts @@ -3,6 +3,7 @@ import { pull } from 'es-toolkit/compat' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { LLink } from '@/lib/litegraph/src/LLink' import { toLinkId } from '@/types/linkId' +import { anchorRerouteChain } from '@/lib/litegraph/src/Reroute' import type { RerouteId } from '@/lib/litegraph/src/Reroute' import type { INodeInputSlot, @@ -85,23 +86,7 @@ export class SubgraphOutput extends SubgraphSlot { slot.links ??= [] slot.links.push(link.id) - // Reroutes - const reroutes = LLink.getReroutes(subgraph, link) - for (const reroute of reroutes) { - reroute.floating = undefined - reroute._dragging = undefined - } - - // If this is the terminus of a floating link, remove it - const lastReroute = reroutes.at(-1) - if (lastReroute) { - for (const linkId of lastReroute.floatingLinkIds) { - const link = subgraph.floatingLinks.get(linkId) - if (link?.parentId === lastReroute.id) { - subgraph.removeFloatingLink(link) - } - } - } + anchorRerouteChain(subgraph, link) subgraph.incrementVersion() node.onConnectionsChange?.( diff --git a/src/stores/linkStore.ts b/src/stores/linkStore.ts index 7b04a9be7f..731e86d865 100644 --- a/src/stores/linkStore.ts +++ b/src/stores/linkStore.ts @@ -84,7 +84,7 @@ export const useLinkStore = defineStore('link', () => { const existing = targets.get(key) if (existing && toRaw(existing) !== toRaw(topology)) return undefined targets.set(key, topology) - return targets.get(key) + return reactive(topology) } /** Removes a link's placement; only the registered topology may vacate it. */