| | 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", |
| | } |
| |
|