mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-11 14:02:37 +08:00
This PR inverts the execution model -- from recursively calling nodes to using a topological sort of the nodes. This change allows for modification of the node graph during execution. This allows for two major advantages: 1. The implementation of lazy evaluation in nodes. For example, if a "Mix Images" node has a mix factor of exactly 0.0, the second image input doesn't even need to be evaluated (and visa-versa if the mix factor is 1.0). 2. Dynamic expansion of nodes. This allows for the creation of dynamic "node groups". Specifically, custom nodes can return subgraphs that replace the original node in the graph. This is an *incredibly* powerful concept. Using this functionality, it was easy to implement: a. Components (a.k.a. node groups) b. Flow control (i.e. while loops) via tail recursion c. All-in-one nodes that replicate the WebUI functionality d. and more All of those were able to be implemented entirely via custom nodes without hooking or replacing any core functionality. Within this PR, I've included all of these proof-of-concepts within a custom node pack. In reality, I would expect some number of them to be merged into the core node set (with the rest left to be implemented by custom nodes). I made very few changes to the front-end, so there are probably some easy UX wins for someone who is more willing to wade into .js land. The user experience is a lot better than I expected though -- progress shows correctly in the UI over the nodes that are being expanded.
105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
import json
|
|
import random
|
|
|
|
# The GraphBuilder is just a utility class that outputs graphs in the form expected by the ComfyUI back-end
|
|
class GraphBuilder:
|
|
def __init__(self, prefix = True):
|
|
if isinstance(prefix, str):
|
|
self.prefix = prefix
|
|
elif prefix:
|
|
self.prefix = "%d.%d." % (random.randint(0, 0xffffffffffffffff), random.randint(0, 0xffffffffffffffff))
|
|
else:
|
|
self.prefix = ""
|
|
self.nodes = {}
|
|
self.id_gen = 1
|
|
|
|
def node(self, class_type, id=None, **kwargs):
|
|
if id is None:
|
|
id = str(self.id_gen)
|
|
self.id_gen += 1
|
|
id = self.prefix + id
|
|
if id in self.nodes:
|
|
return self.nodes[id]
|
|
|
|
node = Node(id, class_type, kwargs)
|
|
self.nodes[id] = node
|
|
return node
|
|
|
|
def lookup_node(self, id):
|
|
id = self.prefix + id
|
|
return self.nodes.get(id)
|
|
|
|
def finalize(self):
|
|
output = {}
|
|
for node_id, node in self.nodes.items():
|
|
output[node_id] = node.serialize()
|
|
return output
|
|
|
|
def replace_node_output(self, node_id, index, new_value):
|
|
node_id = self.prefix + node_id
|
|
to_remove = []
|
|
for node in self.nodes.values():
|
|
for key, value in node.inputs.items():
|
|
if isinstance(value, list) and value[0] == node_id and value[1] == index:
|
|
if new_value is None:
|
|
to_remove.append((node, key))
|
|
else:
|
|
node.inputs[key] = new_value
|
|
for node, key in to_remove:
|
|
del node.inputs[key]
|
|
|
|
def remove_node(self, id):
|
|
id = self.prefix + id
|
|
del self.nodes[id]
|
|
|
|
class Node:
|
|
def __init__(self, id, class_type, inputs):
|
|
self.id = id
|
|
self.class_type = class_type
|
|
self.inputs = inputs
|
|
|
|
def out(self, index):
|
|
return [self.id, index]
|
|
|
|
def set_input(self, key, value):
|
|
if value is None:
|
|
if key in self.inputs:
|
|
del self.inputs[key]
|
|
else:
|
|
self.inputs[key] = value
|
|
|
|
def get_input(self, key):
|
|
return self.inputs.get(key)
|
|
|
|
def serialize(self):
|
|
return {
|
|
"class_type": self.class_type,
|
|
"inputs": self.inputs
|
|
}
|
|
|
|
def add_graph_prefix(graph, outputs, prefix):
|
|
# Change the node IDs and any internal links
|
|
new_graph = {}
|
|
for node_id, node_info in graph.items():
|
|
# Make sure the added nodes have unique IDs
|
|
new_node_id = prefix + node_id
|
|
new_node = { "class_type": node_info["class_type"], "inputs": {} }
|
|
for input_name, input_value in node_info.get("inputs", {}).items():
|
|
if isinstance(input_value, list):
|
|
new_node["inputs"][input_name] = [prefix + input_value[0], input_value[1]]
|
|
else:
|
|
new_node["inputs"][input_name] = input_value
|
|
new_graph[new_node_id] = new_node
|
|
|
|
# Change the node IDs in the outputs
|
|
new_outputs = []
|
|
for n in range(len(outputs)):
|
|
output = outputs[n]
|
|
if isinstance(output, list): # This is a node link
|
|
new_outputs.append([prefix + output[0], output[1]])
|
|
else:
|
|
new_outputs.append(output)
|
|
|
|
return new_graph, tuple(new_outputs)
|
|
|