| from comfy_execution.graph_utils import GraphBuilder, is_link
|
| from comfy_execution.graph import ExecutionBlocker
|
| from .tools import VariantSupport
|
|
|
| NUM_FLOW_SOCKETS = 5
|
| @VariantSupport()
|
| class TestWhileLoopOpen:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(cls):
|
| inputs = {
|
| "required": {
|
| "condition": ("BOOLEAN", {"default": True}),
|
| },
|
| "optional": {
|
| },
|
| }
|
| for i in range(NUM_FLOW_SOCKETS):
|
| inputs["optional"][f"initial_value{i}"] = ("*",)
|
| return inputs
|
|
|
| RETURN_TYPES = tuple(["FLOW_CONTROL"] + ["*"] * NUM_FLOW_SOCKETS)
|
| RETURN_NAMES = tuple(["FLOW_CONTROL"] + [f"value{i}" for i in range(NUM_FLOW_SOCKETS)])
|
| FUNCTION = "while_loop_open"
|
|
|
| CATEGORY = "Testing/Flow"
|
|
|
| def while_loop_open(self, condition, **kwargs):
|
| values = []
|
| for i in range(NUM_FLOW_SOCKETS):
|
| values.append(kwargs.get(f"initial_value{i}", None))
|
| return tuple(["stub"] + values)
|
|
|
| @VariantSupport()
|
| class TestWhileLoopClose:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(cls):
|
| inputs = {
|
| "required": {
|
| "flow_control": ("FLOW_CONTROL", {"rawLink": True}),
|
| "condition": ("BOOLEAN", {"forceInput": True}),
|
| },
|
| "optional": {
|
| },
|
| "hidden": {
|
| "dynprompt": "DYNPROMPT",
|
| "unique_id": "UNIQUE_ID",
|
| }
|
| }
|
| for i in range(NUM_FLOW_SOCKETS):
|
| inputs["optional"][f"initial_value{i}"] = ("*",)
|
| return inputs
|
|
|
| RETURN_TYPES = tuple(["*"] * NUM_FLOW_SOCKETS)
|
| RETURN_NAMES = tuple([f"value{i}" for i in range(NUM_FLOW_SOCKETS)])
|
| FUNCTION = "while_loop_close"
|
|
|
| CATEGORY = "Testing/Flow"
|
|
|
| def explore_dependencies(self, node_id, dynprompt, upstream):
|
| node_info = dynprompt.get_node(node_id)
|
| if "inputs" not in node_info:
|
| return
|
| for k, v in node_info["inputs"].items():
|
| if is_link(v):
|
| parent_id = v[0]
|
| if parent_id not in upstream:
|
| upstream[parent_id] = []
|
| self.explore_dependencies(parent_id, dynprompt, upstream)
|
| upstream[parent_id].append(node_id)
|
|
|
| def collect_contained(self, node_id, upstream, contained):
|
| if node_id not in upstream:
|
| return
|
| for child_id in upstream[node_id]:
|
| if child_id not in contained:
|
| contained[child_id] = True
|
| self.collect_contained(child_id, upstream, contained)
|
|
|
|
|
| def while_loop_close(self, flow_control, condition, dynprompt=None, unique_id=None, **kwargs):
|
| assert dynprompt is not None
|
| if not condition:
|
|
|
| values = []
|
| for i in range(NUM_FLOW_SOCKETS):
|
| values.append(kwargs.get(f"initial_value{i}", None))
|
| return tuple(values)
|
|
|
|
|
| upstream = {}
|
|
|
| self.explore_dependencies(unique_id, dynprompt, upstream)
|
|
|
| contained = {}
|
| open_node = flow_control[0]
|
| self.collect_contained(open_node, upstream, contained)
|
| contained[unique_id] = True
|
| contained[open_node] = True
|
|
|
|
|
|
|
| graph = GraphBuilder()
|
| for node_id in contained:
|
| original_node = dynprompt.get_node(node_id)
|
| node = graph.node(original_node["class_type"], "Recurse" if node_id == unique_id else node_id)
|
| node.set_override_display_id(node_id)
|
| for node_id in contained:
|
| original_node = dynprompt.get_node(node_id)
|
| node = graph.lookup_node("Recurse" if node_id == unique_id else node_id)
|
| assert node is not None
|
| for k, v in original_node["inputs"].items():
|
| if is_link(v) and v[0] in contained:
|
| parent = graph.lookup_node(v[0])
|
| assert parent is not None
|
| node.set_input(k, parent.out(v[1]))
|
| else:
|
| node.set_input(k, v)
|
| new_open = graph.lookup_node(open_node)
|
| assert new_open is not None
|
| for i in range(NUM_FLOW_SOCKETS):
|
| key = f"initial_value{i}"
|
| new_open.set_input(key, kwargs.get(key, None))
|
| my_clone = graph.lookup_node("Recurse")
|
| assert my_clone is not None
|
| result = map(lambda x: my_clone.out(x), range(NUM_FLOW_SOCKETS))
|
| return {
|
| "result": tuple(result),
|
| "expand": graph.finalize(),
|
| }
|
|
|
| @VariantSupport()
|
| class TestExecutionBlockerNode:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(cls):
|
| inputs = {
|
| "required": {
|
| "input": ("*",),
|
| "block": ("BOOLEAN",),
|
| "verbose": ("BOOLEAN", {"default": False}),
|
| },
|
| }
|
| return inputs
|
|
|
| RETURN_TYPES = ("*",)
|
| RETURN_NAMES = ("output",)
|
| FUNCTION = "execution_blocker"
|
|
|
| CATEGORY = "Testing/Flow"
|
|
|
| def execution_blocker(self, input, block, verbose):
|
| if block:
|
| return (ExecutionBlocker("Blocked Execution" if verbose else None),)
|
| return (input,)
|
|
|
| FLOW_CONTROL_NODE_CLASS_MAPPINGS = {
|
| "TestWhileLoopOpen": TestWhileLoopOpen,
|
| "TestWhileLoopClose": TestWhileLoopClose,
|
| "TestExecutionBlocker": TestExecutionBlockerNode,
|
| }
|
| FLOW_CONTROL_NODE_DISPLAY_NAME_MAPPINGS = {
|
| "TestWhileLoopOpen": "While Loop Open",
|
| "TestWhileLoopClose": "While Loop Close",
|
| "TestExecutionBlocker": "Execution Blocker",
|
| }
|
|
|