From eb664f47afa4aadd4db1c78d802637e1affabbb8 Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Thu, 18 Sep 2025 16:06:12 -0700 Subject: [PATCH] Fix cyclic prototype errors with subgraphNodes (#5637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5024 added support for connecting primitive nodes to subgraph inputs. To accomplish this, it pulls WidgetLocator information from the node owning the widget. This `node` property does not exist on all IBaseWidget. `toConcrete` was used to instead have a BaseWidget which is guaranteed to have a node property. The issue that was missed, is that a widget which lacks this information (such as most implemented by custom nodes) sets the node value to the argument which was passed. Here that is the reference to the subgraph node. Sometimes, this `#setWidget` call is made multiple times, and when this occurs, the `input.widget` has itself set as the protoyep, throwing an error. This is resolved by instead taking an additional input which is unambiguous. For reference, this is a near minimal workflow using comfy_mtb that replicates the issue [cyclic.json](https://github.com/user-attachments/files/22412187/cyclic.json) Special thanks to @melMass for assistance discovering this issue. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5637-Fix-cyclic-prototype-errors-with-subgraphNodes-2726d73d365081fea356f5197e4c2b42) by [Unito](https://www.unito.io) --- .../litegraph/src/subgraph/SubgraphNode.ts | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index d5d91aa33..c10a343ad 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -4,7 +4,10 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { LLink, type ResolvedConnection } from '@/lib/litegraph/src/LLink' import { RecursionError } from '@/lib/litegraph/src/infrastructure/RecursionError' -import type { ISubgraphInput } from '@/lib/litegraph/src/interfaces' +import type { + ISubgraphInput, + IWidgetLocator +} from '@/lib/litegraph/src/interfaces' import type { INodeInputSlot, ISlotType, @@ -78,9 +81,10 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { const existingInput = this.inputs.find((i) => i.name == name) if (existingInput) { const linkId = subgraphInput.linkIds[0] - const { inputNode } = subgraph.links[linkId].resolve(subgraph) + const { inputNode, input } = subgraph.links[linkId].resolve(subgraph) const widget = inputNode?.widgets?.find?.((w) => w.name == name) - if (widget) this.#setWidget(subgraphInput, existingInput, widget) + if (widget) + this.#setWidget(subgraphInput, existingInput, widget, input?.widget) return } const input = this.addInput(name, type) @@ -185,13 +189,14 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { subgraphInput.events.addEventListener( 'input-connected', - () => { + (e) => { if (input._widget) return const widget = subgraphInput._widget if (!widget) return - this.#setWidget(subgraphInput, input, widget) + const widgetLocator = e.detail.input.widget + this.#setWidget(subgraphInput, input, widget, widgetLocator) }, { signal } ) @@ -301,7 +306,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { const widget = resolved.inputNode.getWidgetFromSlot(resolved.input) if (!widget) continue - this.#setWidget(subgraphInput, input, widget) + this.#setWidget(subgraphInput, input, widget, resolved.input.widget) break } } @@ -310,11 +315,13 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { #setWidget( subgraphInput: Readonly, input: INodeInputSlot, - widget: Readonly + widget: Readonly, + inputWidget: IWidgetLocator | undefined ) { // Use the first matching widget - const targetWidget = toConcreteWidget(widget, this) - const promotedWidget = targetWidget.createCopyForNode(this) + const promotedWidget = toConcreteWidget(widget, this).createCopyForNode( + this + ) Object.assign(promotedWidget, { get name() { @@ -372,11 +379,9 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { // NOTE: This code creates linked chains of prototypes for passing across // multiple levels of subgraphs. As part of this, it intentionally avoids // creating new objects. Have care when making changes. - const backingInput = - targetWidget.node.findInputSlot(widget.name, true)?.widget ?? {} input.widget ??= { name: subgraphInput.name } input.widget.name = subgraphInput.name - Object.setPrototypeOf(input.widget, backingInput) + if (inputWidget) Object.setPrototypeOf(input.widget, inputWidget) input._widget = promotedWidget }