refactor: extract anchorRerouteChain and collapse chain-splice loops

Deduplicate the connect-path floating-chain cleanup (LGraphNode,
SubgraphInput, SubgraphOutput) into anchorRerouteChain, add
unregisterAllRerouteChains mirroring the link-side helper, splice
createReroute's live/floating loops into one pass over the actual link
objects (also removing the ambiguous cross-id-space lookup for a link
segment), inline the single-caller Reroute.update into setReroute, and
drop test pinia hooks shadowed by the file-level one.
This commit is contained in:
DrJKL
2026-07-04 21:16:45 -07:00
parent 43a4058be9
commit bdf47aebca
7 changed files with 62 additions and 104 deletions

View File

@@ -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)
})

View File

@@ -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

View File

@@ -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

View File

@@ -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<LGraph, 'reroutes'>
): void {
for (const reroute of graph.reroutes.values()) unregisterRerouteChain(reroute)
}

View File

@@ -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', {

View File

@@ -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?.(

View File

@@ -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. */