├── .gitignore
├── workflow.png
├── pyproject.toml
├── floodgate.py
├── .github
└── workflows
│ └── publish.yml
├── LICENSE
├── README.md
└── __init__.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 |
--------------------------------------------------------------------------------
/workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haoming02/comfyui-floodgate/HEAD/workflow.png
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "comfyui-floodgate"
3 | description = "A node that allows you to switch between execution paths"
4 | version = "1.2.0"
5 | license = { text = "MIT License" }
6 | dependencies = []
7 |
8 | [project.urls]
9 | Repository = "https://github.com/Haoming02/comfyui-floodgate"
10 |
11 | [tool.comfy]
12 | PublisherId = "haoming02"
13 | DisplayName = "Floodgate"
14 | Icon = ""
15 |
--------------------------------------------------------------------------------
/floodgate.py:
--------------------------------------------------------------------------------
1 | from comfy.comfy_types.node_typing import IO, ComfyNodeABC, InputTypeDict
2 |
3 |
4 | class FloodGate(ComfyNodeABC):
5 | @classmethod
6 | def INPUT_TYPES(s) -> InputTypeDict:
7 | return {
8 | "required": {
9 | "source": (IO.ANY,),
10 | "gate_open": (IO.BOOLEAN, {"default": False}),
11 | }
12 | }
13 |
14 | RETURN_TYPES = (IO.ANY, IO.ANY)
15 | RETURN_NAMES = ("CLOSE", "OPEN")
16 |
17 | FUNCTION = "gate"
18 | CATEGORY = "utils"
19 |
20 | def gate(self, source, gate_open):
21 | return (source, source)
22 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to Comfy registry
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - "pyproject.toml"
9 |
10 | permissions:
11 | issues: write
12 |
13 | jobs:
14 | publish-node:
15 | name: Publish Custom Node to registry
16 | runs-on: ubuntu-latest
17 | if: ${{ github.repository_owner == 'Haoming02' }}
18 | steps:
19 | - name: Check out code
20 | uses: actions/checkout@v4
21 | - name: Publish Custom Node
22 | uses: Comfy-Org/publish-node-action@v1
23 | with:
24 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Haoming
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ComfyUI Floodgate
2 | This is an Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI), which allows you to easily control the logic flow!
3 |
4 |

5 |
6 | Motivation
7 |
8 | While **Hires. Fix** *(or similar workflows)* can significantly improve the output, it also takes a longer time to process. Thus, many will simply run the base resolution until a good seed is found before enabling it. However, for `ComfyUI` this means connecting and disconnecting multiple nodes every single time...
9 |
10 | Solution
11 |
12 | Introducing, **Floodgate**!
13 | Simply toggle between the logic flows. No more reconnecting multiple nodes!
14 |
15 | How to Use
16 |
17 | Connect the output of a node *(**eg.** `LATENT`)* to the `source` of the Floodgate node, then connect each path to the desired remaining workflow. Simply click on `gate_open` to toggle between the paths to take. The path not chosen will not be executed. Furthermore, since `ComfyUI` caches the intermediate results, opening the Floodgate will not require the precedent nodes to be processed again!
18 |
19 | Features
20 |
21 | 1. Connect any arbitrary types
22 | - *(input/output types still have to match)*
23 | 2. Multiple Floodgates in one workflow
24 | 3. Control each Floodgate individually
25 |
26 |
27 |
28 | **Note:** The logic flow is parsed during the queuing stage, **not** the execution stage. As a result, the boolean value has to be already determined when you press `Queue Prompt`, such as from the toggle or a primitive node. If you use a node that outputs a boolean during execution, this will not work.
29 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | import execution
2 | import nodes
3 |
4 | from .floodgate import FloodGate
5 |
6 | NODE_CLASS_MAPPINGS = {"FloodGate": FloodGate}
7 | NODE_DISPLAY_NAME_MAPPINGS = {"FloodGate": "Flood Gate"}
8 |
9 |
10 | def find_gate(prompt: dict) -> list:
11 | """Find the Unique ID of the Floodgate Node"""
12 | gate_IDs = []
13 |
14 | for k, v in prompt.items():
15 | if v["class_type"] == "FloodGate":
16 | gate_IDs.append(k)
17 |
18 | # if len(gate_IDs) > 1:
19 | # print('[Warning] Multiple Floodgates Detected is still experimental!')
20 |
21 | return gate_IDs
22 |
23 |
24 | def block_gate(prompt: dict, gate_ID: str, floodgate_open: bool) -> dict:
25 | """ "Bypass" the Nodes that should be Blocked"""
26 | nodes_affected = []
27 |
28 | try:
29 | sauce_id, out_index = prompt[gate_ID]["inputs"]["source"]
30 | except KeyError:
31 | # Floodgate is not connected; let ComfyUI raise the error
32 | return prompt
33 |
34 | sauce_class = nodes.NODE_CLASS_MAPPINGS[prompt[sauce_id]["class_type"]]
35 | sauce_type = str(sauce_class.RETURN_TYPES[out_index]).lower().strip()
36 |
37 | for node, data in prompt.items():
38 | for k, v in data["inputs"].items():
39 | if not isinstance(v, list):
40 | continue
41 |
42 | if gate_ID in v:
43 |
44 | target_class = nodes.NODE_CLASS_MAPPINGS[data["class_type"]]
45 | target_type = (
46 | (target_class.INPUT_TYPES()["required"][k][0]).lower().strip()
47 | )
48 |
49 | if sauce_type != target_type:
50 | raise IOError()
51 |
52 | if (not floodgate_open) and (v[1] == 1):
53 | nodes_affected.append(node)
54 | break
55 |
56 | if (floodgate_open) and (v[1] == 0):
57 | nodes_affected.append(node)
58 | break
59 |
60 | for key in nodes_affected:
61 | del prompt[key]
62 |
63 | if len(nodes_affected) > 0:
64 | return recursive_block_gate(prompt, nodes_affected)
65 | else:
66 | return prompt
67 |
68 |
69 | def recursive_block_gate(prompt: dict, node_IDs: list) -> dict:
70 | """Block the subsequent nodes of which source has been blocked"""
71 | to_delete = []
72 |
73 | for node, data in prompt.items():
74 | for k, v in data["inputs"].items():
75 | # Connection is always a List
76 | if not isinstance(v, list):
77 | continue
78 |
79 | if any(ID in v for ID in node_IDs):
80 | to_delete.append(node)
81 | break
82 |
83 | for key in to_delete:
84 | del prompt[key]
85 |
86 | if len(to_delete) > 0:
87 | return recursive_block_gate(prompt, to_delete)
88 | else:
89 | return prompt
90 |
91 |
92 | original_validate = execution.validate_prompt
93 |
94 |
95 | async def hijack_validate(*args):
96 | for arg in args:
97 | if isinstance(arg, dict):
98 | prompt = arg
99 | break
100 |
101 | gate_IDs: list = find_gate(prompt)
102 |
103 | if len(gate_IDs) == 0:
104 | return await original_validate(*args)
105 |
106 | for ID in gate_IDs:
107 | if ID not in prompt.keys():
108 | continue
109 |
110 | try:
111 | gate_open = prompt[ID]["inputs"]["gate_open"]
112 |
113 | if isinstance(gate_open, (tuple, list)) and len(gate_open) == 0:
114 | gate_open = bool(gate_open[0])
115 | if type(gate_open) is bool:
116 | prompt = block_gate(prompt, ID, gate_open)
117 | elif type(gate_open) is list:
118 | sauce_id, conn_id = gate_open
119 |
120 | gate_open = list(prompt[sauce_id]["inputs"].values())[conn_id]
121 | if type(gate_open) is bool:
122 | prompt = block_gate(prompt, ID, gate_open)
123 | else:
124 | raise ValueError
125 | else:
126 | raise ValueError
127 |
128 | except IOError:
129 | return (
130 | False,
131 | {
132 | "type": "floodgate_io_mismatch",
133 | "message": "Floodgate IO Type Mismatch",
134 | "details": "source cannot be connected to outputs",
135 | "extra_info": {},
136 | },
137 | [],
138 | [],
139 | )
140 |
141 | except ValueError:
142 | return (
143 | False,
144 | {
145 | "type": "floodgate_invalid_boolean",
146 | "message": "Floodgate Unable to Determine Boolean",
147 | "details": "please use a primitive boolean node",
148 | "extra_info": {},
149 | },
150 | [],
151 | [],
152 | )
153 |
154 | return await original_validate(*args)
155 |
156 |
157 | execution.validate_prompt = hijack_validate
158 |
--------------------------------------------------------------------------------