mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 15:40:10 +00:00
[backport core/1.30] Fix partial execution inside subgraphs (#6704)
Backport of #6487 to `core/1.30` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6704-backport-core-1-30-Fix-partial-execution-inside-subgraphs-2ab6d73d365081d09667e6c44528ea74) by [Unito](https://www.unito.io) Co-authored-by: AustinMroz <austin@comfy.org>
This commit is contained in:
@@ -501,6 +501,15 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
// Get execution IDs for all selected output nodes and their descendants
|
||||
const executionIds =
|
||||
getExecutionIdsForSelectedNodes(selectedOutputNodes)
|
||||
if (executionIds.length === 0) {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.failedToQueue'),
|
||||
detail: t('toastMessages.failedExecutionPathResolution'),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
await app.queuePrompt(0, batchCount, executionIds)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1456,6 +1456,8 @@
|
||||
"toastMessages": {
|
||||
"nothingToQueue": "Nothing to queue",
|
||||
"pleaseSelectOutputNodes": "Please select output nodes",
|
||||
"failedToQueue": "Failed to queue",
|
||||
"failedExecutionPathResolution": "Could not resolve path to selected nodes",
|
||||
"no3dScene": "No 3D scene to apply texture",
|
||||
"failedToApplyTexture": "Failed to apply texture",
|
||||
"no3dSceneToExport": "No 3D scene to export",
|
||||
|
||||
@@ -520,8 +520,16 @@ export function collectFromNodes<T = LGraphNode, C = void>(
|
||||
* @returns Array of execution IDs for selected nodes and all nodes within selected subgraphs
|
||||
*/
|
||||
export function getExecutionIdsForSelectedNodes(
|
||||
selectedNodes: LGraphNode[]
|
||||
selectedNodes: LGraphNode[],
|
||||
startGraph = selectedNodes[0]?.graph
|
||||
): NodeExecutionId[] {
|
||||
if (!startGraph) return []
|
||||
const rootGraph = startGraph.rootGraph
|
||||
const parentPath = startGraph.isRootGraph
|
||||
? ''
|
||||
: findPartialExecutionPathToGraph(startGraph, rootGraph)
|
||||
if (parentPath === undefined) return []
|
||||
|
||||
return collectFromNodes<NodeExecutionId, string>(selectedNodes, {
|
||||
collector: (node, parentExecutionId) => {
|
||||
const nodeId = String(node.id)
|
||||
@@ -531,7 +539,22 @@ export function getExecutionIdsForSelectedNodes(
|
||||
const nodeId = String(node.id)
|
||||
return parentExecutionId ? `${parentExecutionId}:${nodeId}` : nodeId
|
||||
},
|
||||
initialContext: '',
|
||||
initialContext: parentPath,
|
||||
expandSubgraphs: true
|
||||
})
|
||||
}
|
||||
|
||||
function findPartialExecutionPathToGraph(
|
||||
target: LGraph,
|
||||
root: LGraph
|
||||
): string | undefined {
|
||||
for (const node of root.nodes) {
|
||||
if (!node.isSubgraphNode()) continue
|
||||
|
||||
if (node.subgraph === target) return `${node.id}`
|
||||
|
||||
const subpath = findPartialExecutionPathToGraph(target, node.subgraph)
|
||||
if (subpath !== undefined) return node.id + ':' + subpath
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -35,14 +35,18 @@ function createMockNode(
|
||||
isSubgraph?: boolean
|
||||
subgraph?: Subgraph
|
||||
callback?: () => void
|
||||
graph?: LGraph
|
||||
} = {}
|
||||
): LGraphNode {
|
||||
return {
|
||||
const node = {
|
||||
id,
|
||||
isSubgraphNode: options.isSubgraph ? () => true : undefined,
|
||||
subgraph: options.subgraph,
|
||||
onExecutionStart: options.callback
|
||||
onExecutionStart: options.callback,
|
||||
graph: options.graph
|
||||
} as unknown as LGraphNode
|
||||
options.graph?.nodes?.push(node)
|
||||
return node
|
||||
}
|
||||
|
||||
// Mock graph factory
|
||||
@@ -50,20 +54,28 @@ function createMockGraph(nodes: LGraphNode[]): LGraph {
|
||||
return {
|
||||
_nodes: nodes,
|
||||
nodes: nodes,
|
||||
isRootGraph: true,
|
||||
getNodeById: (id: string | number) =>
|
||||
nodes.find((n) => String(n.id) === String(id)) || null
|
||||
} as unknown as LGraph
|
||||
}
|
||||
|
||||
// Mock subgraph factory
|
||||
function createMockSubgraph(id: string, nodes: LGraphNode[]): Subgraph {
|
||||
return {
|
||||
function createMockSubgraph(
|
||||
id: string,
|
||||
nodes: LGraphNode[],
|
||||
rootGraph?: LGraph
|
||||
): Subgraph {
|
||||
const graph = {
|
||||
id,
|
||||
_nodes: nodes,
|
||||
nodes: nodes,
|
||||
isRootGraph: false,
|
||||
rootGraph,
|
||||
getNodeById: (nodeId: string | number) =>
|
||||
nodes.find((n) => String(n.id) === String(nodeId)) || null
|
||||
} as unknown as Subgraph
|
||||
return graph
|
||||
}
|
||||
|
||||
describe('graphTraversalUtil', () => {
|
||||
@@ -983,31 +995,30 @@ describe('graphTraversalUtil', () => {
|
||||
|
||||
describe('getExecutionIdsForSelectedNodes', () => {
|
||||
it('should return simple IDs for top-level nodes', () => {
|
||||
const nodes = [
|
||||
createMockNode('123'),
|
||||
createMockNode('456'),
|
||||
createMockNode('789')
|
||||
]
|
||||
const graph = createMockGraph([])
|
||||
createMockNode('123', { graph })
|
||||
createMockNode('456', { graph })
|
||||
createMockNode('789', { graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(nodes)
|
||||
const executionIds = getExecutionIdsForSelectedNodes(graph.nodes)
|
||||
|
||||
expect(executionIds).toEqual(['789', '456', '123']) // DFS processes in LIFO order
|
||||
})
|
||||
|
||||
it('should expand subgraph nodes to include all children', () => {
|
||||
const graph = createMockGraph([])
|
||||
const subNodes = [createMockNode('10'), createMockNode('11')]
|
||||
const subgraph = createMockSubgraph('sub-uuid', subNodes)
|
||||
const nodes = [
|
||||
createMockNode('1'),
|
||||
createMockNode('2', { isSubgraph: true, subgraph })
|
||||
]
|
||||
createMockNode('1', { graph })
|
||||
createMockNode('2', { isSubgraph: true, subgraph, graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(nodes)
|
||||
const executionIds = getExecutionIdsForSelectedNodes(graph.nodes)
|
||||
|
||||
expect(executionIds).toEqual(['2', '2:10', '2:11', '1']) // DFS: node 2 first, then its children
|
||||
})
|
||||
|
||||
it('should handle deeply nested subgraphs correctly', () => {
|
||||
const graph = createMockGraph([])
|
||||
const deepNodes = [createMockNode('30'), createMockNode('31')]
|
||||
const deepSubgraph = createMockSubgraph('deep-uuid', deepNodes)
|
||||
|
||||
@@ -1019,7 +1030,8 @@ describe('graphTraversalUtil', () => {
|
||||
|
||||
const topNode = createMockNode('10', {
|
||||
isSubgraph: true,
|
||||
subgraph: midSubgraph
|
||||
subgraph: midSubgraph,
|
||||
graph
|
||||
})
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes([topNode])
|
||||
@@ -1028,16 +1040,15 @@ describe('graphTraversalUtil', () => {
|
||||
})
|
||||
|
||||
it('should handle mixed selection of regular and subgraph nodes', () => {
|
||||
const graph = createMockGraph([])
|
||||
const subNodes = [createMockNode('100'), createMockNode('101')]
|
||||
const subgraph = createMockSubgraph('sub-uuid', subNodes)
|
||||
|
||||
const nodes = [
|
||||
createMockNode('1'),
|
||||
createMockNode('2', { isSubgraph: true, subgraph }),
|
||||
createMockNode('3')
|
||||
]
|
||||
createMockNode('1', { graph })
|
||||
createMockNode('2', { isSubgraph: true, subgraph, graph })
|
||||
createMockNode('3', { graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(nodes)
|
||||
const executionIds = getExecutionIdsForSelectedNodes(graph.nodes)
|
||||
|
||||
expect(executionIds).toEqual([
|
||||
'3',
|
||||
@@ -1054,10 +1065,12 @@ describe('graphTraversalUtil', () => {
|
||||
})
|
||||
|
||||
it('should handle subgraph with no children', () => {
|
||||
const graph = createMockGraph([])
|
||||
const emptySubgraph = createMockSubgraph('empty-uuid', [])
|
||||
const node = createMockNode('1', {
|
||||
isSubgraph: true,
|
||||
subgraph: emptySubgraph
|
||||
subgraph: emptySubgraph,
|
||||
graph
|
||||
})
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes([node])
|
||||
@@ -1079,9 +1092,11 @@ describe('graphTraversalUtil', () => {
|
||||
currentSubgraph = createMockSubgraph(`deep-${i}`, [node])
|
||||
}
|
||||
|
||||
const graph = createMockGraph([])
|
||||
const topNode = createMockNode('1', {
|
||||
isSubgraph: true,
|
||||
subgraph: currentSubgraph
|
||||
subgraph: currentSubgraph,
|
||||
graph
|
||||
})
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes([topNode])
|
||||
@@ -1103,12 +1118,11 @@ describe('graphTraversalUtil', () => {
|
||||
createMockNode('101') // Same ID as in subgraph1
|
||||
])
|
||||
|
||||
const nodes = [
|
||||
createMockNode('1', { isSubgraph: true, subgraph: subgraph1 }),
|
||||
createMockNode('2', { isSubgraph: true, subgraph: subgraph2 })
|
||||
]
|
||||
const graph = createMockGraph([])
|
||||
createMockNode('1', { isSubgraph: true, subgraph: subgraph1, graph })
|
||||
createMockNode('2', { isSubgraph: true, subgraph: subgraph2, graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(nodes)
|
||||
const executionIds = getExecutionIdsForSelectedNodes(graph.nodes)
|
||||
|
||||
expect(executionIds).toEqual([
|
||||
'2',
|
||||
@@ -1128,9 +1142,11 @@ describe('graphTraversalUtil', () => {
|
||||
}
|
||||
const bigSubgraph = createMockSubgraph('big-uuid', manyNodes)
|
||||
|
||||
const graph = createMockGraph([])
|
||||
const node = createMockNode('parent', {
|
||||
isSubgraph: true,
|
||||
subgraph: bigSubgraph
|
||||
subgraph: bigSubgraph,
|
||||
graph
|
||||
})
|
||||
|
||||
const start = performance.now()
|
||||
@@ -1157,19 +1173,17 @@ describe('graphTraversalUtil', () => {
|
||||
})
|
||||
const midSubgraph = createMockSubgraph('mid-uuid', [midNode1, midNode2])
|
||||
|
||||
const topNode = createMockNode('100', {
|
||||
isSubgraph: true,
|
||||
subgraph: midSubgraph
|
||||
})
|
||||
|
||||
const graph = createMockGraph([])
|
||||
// Select nodes at different nesting levels
|
||||
const selectedNodes = [
|
||||
createMockNode('1'), // Root level
|
||||
topNode, // Contains subgraph
|
||||
createMockNode('2') // Root level
|
||||
]
|
||||
createMockNode('100', {
|
||||
isSubgraph: true,
|
||||
subgraph: midSubgraph,
|
||||
graph
|
||||
})
|
||||
createMockNode('1', { graph })
|
||||
createMockNode('2', { graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(selectedNodes)
|
||||
const executionIds = getExecutionIdsForSelectedNodes(graph.nodes)
|
||||
|
||||
expect(executionIds).toContain('1')
|
||||
expect(executionIds).toContain('2')
|
||||
@@ -1178,6 +1192,17 @@ describe('graphTraversalUtil', () => {
|
||||
expect(executionIds).toContain('100:202')
|
||||
expect(executionIds).toContain('100:202:300')
|
||||
})
|
||||
it('should resolve full execution path of a node inside a subgraph', () => {
|
||||
const graph = createMockGraph([])
|
||||
const subgraph = createMockSubgraph('sub-uuid', [], graph)
|
||||
createMockNode('11', { graph: subgraph })
|
||||
createMockNode('10', { graph: subgraph })
|
||||
createMockNode('2', { isSubgraph: true, subgraph, graph })
|
||||
|
||||
const executionIds = getExecutionIdsForSelectedNodes(subgraph.nodes)
|
||||
|
||||
expect(executionIds).toEqual(['2:10', '2:11'])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user