Skip to content

Commit 8ae4d49

Browse files
committed
Upstream graph execution traversal logic to litegraph
Initial impl; inefficient & can be optimised with a map (#4270).
1 parent 908652d commit 8ae4d49

File tree

2 files changed

+80
-66
lines changed

2 files changed

+80
-66
lines changed

src/utils/executionUtil.ts

Lines changed: 33 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import type { LGraph, LGraphNode, NodeId } from '@comfyorg/litegraph'
2-
import { LGraphEventMode } from '@comfyorg/litegraph'
1+
import type { LGraph, NodeId } from '@comfyorg/litegraph'
2+
import {
3+
ExecutableNodeDTO,
4+
LGraphEventMode,
5+
SubgraphNode
6+
} from '@comfyorg/litegraph'
37

48
import type {
59
ComfyApiWorkflow,
@@ -74,20 +78,31 @@ export const graphToPrompt = async (
7478
workflow.extra ??= {}
7579
workflow.extra.frontendVersion = __COMFYUI_FRONTEND_VERSION__
7680

81+
const computedNodeDtos = graph
82+
.computeExecutionOrder(false)
83+
.map(
84+
(node) =>
85+
new ExecutableNodeDTO(
86+
node,
87+
[],
88+
node instanceof SubgraphNode ? node : undefined
89+
)
90+
)
91+
7792
let output: ComfyApiWorkflow = {}
7893
// Process nodes in order of execution
79-
for (const outerNode of graph.computeExecutionOrder(false)) {
80-
const skipNode =
94+
for (const outerNode of computedNodeDtos) {
95+
// Don't serialize muted nodes
96+
if (
8197
outerNode.mode === LGraphEventMode.NEVER ||
8298
outerNode.mode === LGraphEventMode.BYPASS
83-
const innerNodes =
84-
!skipNode && outerNode.getInnerNodes
85-
? outerNode.getInnerNodes()
86-
: [outerNode]
87-
for (const node of innerNodes) {
99+
) {
100+
continue
101+
}
102+
103+
for (const node of outerNode.getInnerNodes()) {
88104
if (
89105
node.isVirtualNode ||
90-
// Don't serialize muted nodes
91106
node.mode === LGraphEventMode.NEVER ||
92107
node.mode === LGraphEventMode.BYPASS
93108
) {
@@ -120,58 +135,14 @@ export const graphToPrompt = async (
120135

121136
// Store all node links
122137
for (const [i, input] of node.inputs.entries()) {
123-
let parent: LGraphNode | null | undefined = node.getInputNode(i)
124-
if (!parent) continue
125-
126-
let link = node.getInputLink(i)
127-
while (
128-
parent?.mode === LGraphEventMode.BYPASS ||
129-
parent?.isVirtualNode
130-
) {
131-
if (!link) break
132-
133-
if (parent.isVirtualNode) {
134-
link = parent.getInputLink(link.origin_slot)
135-
if (!link) break
136-
137-
parent = parent.isSubgraphNode()
138-
? parent.resolveSubgraphOutputLink(link.origin_slot)?.outputNode
139-
: parent.getInputNode(link.target_slot)
140-
141-
if (!parent) break
142-
} else if (!parent.inputs) {
143-
// Maintains existing behaviour if parent.getInputLink is overriden
144-
break
145-
} else if (parent.mode === LGraphEventMode.BYPASS) {
146-
// Bypass nodes by finding first input with matching type
147-
const parentInputIndexes = Object.keys(parent.inputs).map(Number)
148-
// Prioritise exact slot index
149-
const indexes = [link.origin_slot].concat(parentInputIndexes)
150-
151-
const matchingIndex = indexes.find(
152-
(index) => parent?.inputs[index]?.type === input.type
153-
)
154-
// No input types match
155-
if (matchingIndex === undefined) break
156-
157-
link = parent.getInputLink(matchingIndex)
158-
if (link) parent = parent.getInputNode(matchingIndex)
159-
}
160-
}
161-
162-
if (link) {
163-
if (parent?.updateLink) {
164-
// Subgraph node / groupNode callback; deprecated, should be replaced
165-
link = parent.updateLink(link)
166-
}
167-
if (link) {
168-
inputs[input.name] = [
169-
String(link.origin_id),
170-
// @ts-expect-error link.origin_slot is already number.
171-
parseInt(link.origin_slot)
172-
]
173-
}
174-
}
138+
const resolvedInput = node.resolveInput(i)
139+
if (!resolvedInput) continue
140+
141+
inputs[input.name] = [
142+
String(resolvedInput.origin_id),
143+
// @ts-expect-error link.origin_slot is already number.
144+
parseInt(resolvedInput.origin_slot)
145+
]
175146
}
176147

177148
output[String(node.id)] = {

src/utils/litegraphUtil.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { ColorOption, LGraph, Reroute } from '@comfyorg/litegraph'
22
import { LGraphGroup, LGraphNode, isColorable } from '@comfyorg/litegraph'
3-
import type { ISerialisedGraph } from '@comfyorg/litegraph/dist/types/serialisation'
3+
import type {
4+
ExportedSubgraph,
5+
ISerialisableNodeInput,
6+
ISerialisedGraph
7+
} from '@comfyorg/litegraph/dist/types/serialisation'
48
import type {
59
IBaseWidget,
610
IComboWidget
@@ -167,12 +171,11 @@ export function fixLinkInputSlots(graph: LGraph) {
167171
* This should match the serialization format of legacy widget conversion.
168172
*
169173
* @param graph - The graph to compress widget input slots for.
174+
* @throws If an infinite loop is detected.
170175
*/
171176
export function compressWidgetInputSlots(graph: ISerialisedGraph) {
172177
for (const node of graph.nodes) {
173-
node.inputs = node.inputs?.filter(
174-
(input) => !(input.widget && input.link === null)
175-
)
178+
node.inputs = node.inputs?.filter(matchesLegacyApi)
176179

177180
for (const [inputIndex, input] of node.inputs?.entries() ?? []) {
178181
if (input.link) {
@@ -183,4 +186,44 @@ export function compressWidgetInputSlots(graph: ISerialisedGraph) {
183186
}
184187
}
185188
}
189+
190+
compressSubgraphWidgetInputSlots(graph.definitions?.subgraphs)
191+
}
192+
193+
function matchesLegacyApi(input: ISerialisableNodeInput) {
194+
return !(input.widget && input.link === null)
195+
}
196+
197+
/**
198+
* Duplication to handle the legacy link arrays in the root workflow.
199+
* @see compressWidgetInputSlots
200+
* @param subgraph The subgraph to compress widget input slots for.
201+
*/
202+
function compressSubgraphWidgetInputSlots(
203+
subgraphs: ExportedSubgraph[] | undefined,
204+
visited = new WeakSet<ExportedSubgraph>()
205+
) {
206+
if (!subgraphs) return
207+
208+
for (const subgraph of subgraphs) {
209+
if (visited.has(subgraph)) throw new Error('Infinite loop detected')
210+
visited.add(subgraph)
211+
212+
if (subgraph.nodes) {
213+
for (const node of subgraph.nodes) {
214+
node.inputs = node.inputs?.filter(matchesLegacyApi)
215+
216+
if (!subgraph.links) continue
217+
218+
for (const [inputIndex, input] of node.inputs?.entries() ?? []) {
219+
if (input.link) {
220+
const link = subgraph.links.find((link) => link.id === input.link)
221+
if (link) link.target_slot = inputIndex
222+
}
223+
}
224+
}
225+
}
226+
227+
compressSubgraphWidgetInputSlots(subgraph.definitions?.subgraphs, visited)
228+
}
186229
}

0 commit comments

Comments
 (0)