From d5ebbbaf41e848d969e20f6fc316aabb1b337350 Mon Sep 17 00:00:00 2001 From: duanyll Date: Wed, 19 Nov 2025 12:13:46 +0800 Subject: [PATCH] add documents --- README.md | 129 ++++++++++++++++++++++++++--------------- docs/node-reference.md | 54 +++++++++++++++++ docs/usage.md | 103 ++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 46 deletions(-) create mode 100644 docs/node-reference.md create mode 100644 docs/usage.md diff --git a/README.md b/README.md index 88b4d5d..7ad48f2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,91 @@ # ComfyUI Functional -Easy functional programming and flow control within ComfyUI +Functional programming primitives for ComfyUI graphs: define functions inline, call them multiple times, and build loops, recursion, and other control-flow constructs without leaving the node editor. -> [!NOTE] -> This projected was created with a [cookiecutter](https://github.com/Comfy-Org/cookiecutter-comfy-extension) template. It helps you start writing custom nodes without worrying about the Python setup. +> ⚠️ This extension leans on a few hacky ComfyUI internals (scheduler hooks, coroutine runners, cache invalidation). Expect sharp edges and test workflows before relying on them in production pipelines. -## Quickstart +- English docs: [`docs/usage.md`](./docs/usage.md) · [`docs/node-reference.md`](./docs/node-reference.md) +- 中文使用说明: https://duanyll.com/2025/9/25/ComfyUI-Functional/ -1. Install [ComfyUI](https://docs.comfy.org/get_started). -1. Install [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) -1. Look up this extension in ComfyUI-Manager. If you are installing manually, clone this repository under `ComfyUI/custom_nodes`. -1. Restart ComfyUI. +![Defining a function in ComfyUI](https://img.duanyll.com/img/fa65de7f.png) -# Features +## Why ComfyUI-Functional? -- A list of features +- **Function definitions inside graphs** – Drop `Function Parameter` and `Function End` nodes to encapsulate any node subgraph. Call it repeatedly or pass it to other nodes just like a Python closure. +- **Reusable, composable control flow** – `Map`, `Fold`, `Nest`, `Select`, and friends let you describe loops and branching logic without custom Python code. Bring your own functions as inputs. +- **Side-effect helpers** – `Sow`, `Reap`, `Inspect`, and `Sleep` make it possible to log data, accumulate results, and debug execution order when building complex flows. +- **Interop with other packs** – Pairs especially well with [Basic Data Handling](https://github.com/StableLlama/ComfyUI-basic_data_handling); use its `LIST` values whenever you pass arrays through the functional nodes. -## Develop +## Installation -To install the dev dependencies and pre-commit (will run the ruff hook), do: +1. Install [ComfyUI](https://docs.comfy.org/get_started) and (optionally) [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager). +2. Search for `Duanyll/comfyui_functional` inside ComfyUI-Manager and click install. +3. Manual alternative: clone this repository into `ComfyUI/custom_nodes` and restart ComfyUI. +4. Install [Basic Data Handling](https://github.com/StableLlama/ComfyUI-basic_data_handling); convert ComfyUI Data Lists to `LIST` before feeding them into these nodes. +5. Load one of the JSON files under `example_workflows/` to see the nodes in action. + +> 💡 `example_workflows/functional-nested-calls.json` mirrors the blog's nested function demo and is a great starting point for understanding how functions call other functions. + +## Core Concepts + +### Define a function + +1. Add one `Function Parameter` node per argument. Rename them so the order is obvious. +2. Build any graph using those parameters. +3. Finish the graph with a `Function End` node. Each function returns exactly one value—wrap multiple outputs into a `LIST`. + +### Call a function + +Connect the `Function End` output into a `Call Function` node, wire its inputs in the same order, and use the return value anywhere in your workflow. You can reuse the same function instance across multiple Call Function nodes, or even feed the function back into itself for recursion. Always guard recursion with a termination condition to avoid infinite loops. + +![Calling a function multiple times](https://img.duanyll.com/img/dfc6e532.png) + +### Looping and branching with high-order nodes + +Use the high-order nodes under `duanyll/functional/high_order` to iterate over lists or control execution: + +- `Map`/`MapIndexed` to transform lists. +- `Fold` to accumulate state across a list. +- `Nest`/`NestWhile` to repeat a function a fixed number of times or until a predicate fails. +- `Select`/`TakeWhile` to filter values. +- `Comap` to fan a single input into multiple processing branches. + +See [`docs/node-reference.md`](./docs/node-reference.md) for the full signature cheat sheet. + +![Mapping across a list](https://img.duanyll.com/img/1459c201.png) + +### Side effects and debugging + +- **Sow/Reap** collect intermediate results during a run. Chain the `signal` sockets to enforce ordering whenever side effects matter. +- **Inspect** reads any value in the middle of the function body without forcing the graph to evaluate eagerly. Pair with `Sleep` if the output blinks too fast in the viewer. +- Whenever side effects are detected the extension disables ComfyUI's caching to keep the results honest; expect reruns to take longer than pure functional graphs. + +![Collecting values with Sow and Reap](https://img.duanyll.com/img/f60c2562.png) + +## Troubleshooting Cheatsheet + +- **"Prompt has no outputs"** – You either forgot to call the function or an output still depends on `Function Parameter` without being routed through `Function End`. +- **Graph freezes** – Make sure you are passing `LIST` values (from Basic Data Handling) rather than ComfyUI's built-in Data List sockets. +- **Infinite loops** – Configure `NestWhile` predicates and `max_depth` caps, or add counters into your Fold bodies. +- **Stale debug output** – Ensure something depends on the `signal` coming out of `Inspect`; otherwise it will never execute. + +## Example Workflows + +``` +example_workflows/ +├── functional-map.json # Mapping numeric transforms across a LIST +├── functional-list-param.json # Multiple parameter inputs + LIST handling +├── functional-nested-calls.json # Functions calling other functions +├── functional-recursion.json # Recursive calls with a guard +├── functional-sow-reap.json # Collecting values with Sow/Reap + Inspect +└── functional-inspect.json # Inspect/Sleep techniques for debugging +``` + +Feel free to copy these into your own projects and adapt the prompt data to match your checkpoints and samplers. + +## Development + +To hack on the nodes locally: ```bash cd comfyui_functional @@ -26,42 +93,12 @@ pip install -e .[dev] pre-commit install ``` -The `-e` flag above will result in a "live" install, in the sense that any changes you make to your node extension will automatically be picked up the next time you run ComfyUI. - -## Publish to Github - -Install Github Desktop or follow these [instructions](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) for ssh. - -1. Create a Github repository that matches the directory name. -2. Push the files to Git -``` -git add . -git commit -m "project scaffolding" -git push -``` - -## Writing custom nodes - -An example custom node is located in [node.py](src/comfyui_functional/nodes.py). To learn more, read the [docs](https://docs.comfy.org/essentials/custom_node_overview). - +The editable (`-e`) install allows ComfyUI to pick up changes immediately. Run the Ruff + pytest hooks via `pre-commit run --all-files` before submitting patches. ## Tests -This repo contains unit tests written in Pytest in the `tests/` directory. It is recommended to unit test your custom node. +Pytest suites live under `tests/`. Run them with `pytest` or rely on the GitHub Actions workflows that ship with this template (`build-pipeline.yml` for lint/tests, `validate.yml` for node-diff checks). -- [build-pipeline.yml](.github/workflows/build-pipeline.yml) will run pytest and linter on any open PRs -- [validate.yml](.github/workflows/validate.yml) will run [node-diff](https://github.com/Comfy-Org/node-diff) to check for breaking changes - -## Publishing to Registry - -If you wish to share this custom node with others in the community, you can publish it to the registry. We've already auto-populated some fields in `pyproject.toml` under `tool.comfy`, but please double-check that they are correct. - -You need to make an account on https://registry.comfy.org and create an API key token. - -- [ ] Go to the [registry](https://registry.comfy.org). Login and create a publisher id (everything after the `@` sign on your registry profile). -- [ ] Add the publisher id into the pyproject.toml file. -- [ ] Create an api key on the Registry for publishing from Github. [Instructions](https://docs.comfy.org/registry/publishing#create-an-api-key-for-publishing). -- [ ] Add it to your Github Repository Secrets as `REGISTRY_ACCESS_TOKEN`. - -A Github action will run on every git push. You can also run the Github action manually. Full instructions [here](https://docs.comfy.org/registry/publishing). Join our [discord](https://discord.com/invite/comfyorg) if you have any questions! +## Publishing to the Registry +Double-check the metadata under `[tool.comfy]` in `pyproject.toml`, set up an account on https://registry.comfy.org, and configure `REGISTRY_ACCESS_TOKEN` in your repo settings before enabling the provided publish workflow. See the [official instructions](https://docs.comfy.org/registry/publishing) for the full process. diff --git a/docs/node-reference.md b/docs/node-reference.md new file mode 100644 index 0000000..a5b9143 --- /dev/null +++ b/docs/node-reference.md @@ -0,0 +1,54 @@ +# ComfyUI Functional Node Reference + +This document complements the [Usage Guide](./usage.md) with node-by-node details. All names match the categories that appear inside ComfyUI (`duanyll/functional/*`). + +## Function Building Blocks + +| Node | Description | Tips | +| --- | --- | --- | +| `Function Parameter` | Declares a positional parameter. The label and optional index determine the parameter order. | You can rename each node to clarify semantics (e.g., `mask`, `strength`). | +| `Function End` | Marks the return value of the function body. | Only one output is supported; bundle extra values into a `LIST`. | +| `Call Function` | Executes a function object and returns its output. | Connect parameters in the exact order created by Function Parameter nodes. | + +## High-Order Nodes + +All high-order nodes accept function objects in their first socket. They rely on Python coroutines so they can invoke your function multiple times during a single ComfyUI execution. + +| Node | Signature | Use Cases | Notes | +| --- | --- | --- | --- | +| `Comap` | `(functions LIST, value)` | Compare different processing branches with the same input. | Provide a `LIST` of function objects. Each result is collected into a `LIST` in the same order. | +| `Fold` | `(function, initial_value, data LIST)` | Running accumulation (reduce). Useful for sums, chained transforms, or building strings. | Your function receives `(accumulator, element)`. | +| `Map` | `(function, data LIST)` | Apply the function to each element independently. | Returns a `LIST` with the same length as the input. | +| `MapIndexed` | `(function, data LIST)` | Like Map, but exposes the index. | Your function receives `(value, index)` with the index starting at `1`. | +| `Nest` | `(function, value, iterations INT)` | Repeat the same processing step a fixed number of times. | Equivalent to calling the function n times in a loop. | +| `NestWhile` | `(function, value, predicate, max_depth INT)` | `while` loop semantics. | After each iteration the predicate runs; if it returns `False`, iteration stops early. Use `max_depth` as a safety cap. | +| `Select` | `(predicate, data LIST)` | Filter a list according to a predicate. | Predicate must return a BOOLEAN. | +| `TakeWhile` | `(predicate, data LIST)` | Consume values until the predicate fails. | Helpful when streaming or chunking data. | + +## Side-Effect Helpers + +| Node | Purpose | Wiring Notes | +| --- | --- | --- | +| `Sow` | Push a value (and optional `tag`) into an implicit list. | Chain the `signal` port through any nodes that must run before the Sow executes. | +| `Reap` | Retrieve everything previously sown within the same function call. | Usually the final step of a loop. The `signal` output should continue toward downstream nodes. | +| `Inspect` | Tap into intermediate values for debugging without forcing the branch to evaluate at graph-build time. | Connect the probed value straight into the second output socket. The node only runs when its `signal` output is consumed. | +| `Sleep` | Delay execution for the specified number of seconds. | Handy when you need time to study Inspect output in the UI. | + +## Locgical Utilities Nodes + +| Node | Purpose | +| --- | --- | +| `IfCondition` | Standard if-then-else branching. Only selected branch executes. | +| `LogicalAnd` | Logical AND operation on boolean inputs. Short-circuits on first `False`. | +| `LogicalOr` | Logical OR operation on boolean inputs. Short-circuits on first `True`. | + +## Utilities and Extensions + +- **`CoroutineNodeBase`** (Python class): base class for high-order nodes. Check `comfyui_functional/high_level.py` for examples of implementing `run_coroutine`. +- **`example_workflows/`**: reference graphs for Map/Select pipelines, nested calls, recursion, and Sow/Reap debugging patterns. + +## Limitations + +- All nodes expect `LIST` data structures from the Basic Data Handling pack. Passing ComfyUI Data Lists will deadlock the graph. +- The scheduler hacks that make repeated execution possible may disable caching. Budget extra runtime whenever you lean on side effects. +- ComfyUI updates can invalidate assumptions about evaluation order. Test after every upgrade and file issues with reproduction workflows when possible. diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..31a95b1 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,103 @@ +# ComfyUI Functional Usage Guide + +This guide walks through the day-to-day workflow of using ComfyUI-Functional. For Chinese-language instructions, please refer to https://duanyll.com/2025/9/25/ComfyUI-Functional/. + +## Prerequisites + +- A working [ComfyUI](https://docs.comfy.org/get_started) installation. +- [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) for one-click installs (optional, but recommended). +- The [Basic Data Handling](https://github.com/StableLlama/ComfyUI-basic_data_handling) node pack. Always use its `LIST` data type when passing arrays into or out of the functional nodes. + +## Installing or Updating the Extension + +1. Install through ComfyUI-Manager by searching for `comfyui_functional`. +2. Manual installation: clone this repository under `ComfyUI/custom_nodes` and restart ComfyUI. +3. Load the sample graphs found under `example_workflows/` in this repo to experiment with the nodes. + +> ⚠️ This node pack is intentionally hacky: it reaches into ComfyUI's scheduler to run and reuse node outputs. Expect breaking changes whenever ComfyUI is updated and keep test workflows handy. + +## Building Functions Inside ComfyUI + +1. Drop **Function Parameter** nodes for each argument your function accepts. Double-click the node title to rename it for clarity. +2. Wire the parameters into the computation graph that forms the function body. +3. Connect the last node of the body to a **Function End** node. Each function can return only one value; bundle multiple outputs into a `LIST` if needed. + +![Defining a simple function](https://img.duanyll.com/img/fa65de7f.png) + +Tips: +- Nodes that do not depend on Function Parameter inputs are evaluated a single time during function definition and reused for every call. This makes it safe to load checkpoints or text encoders inside a function. +- When you see `Prompt has no outputs`, it usually means you forgot to connect the result to `Function End` or to actually call the function (see below). + +## Calling Functions and Reusing Graphs + +1. Add a **Call Function** node. +2. Connect the `Function` input to the `Function End` output. +3. Provide arguments in the same order as the Function Parameter nodes. +4. Use the `Return` output like a regular node output anywhere in your workflow. + +![Calling a function](https://img.duanyll.com/img/8a4f8feb.png) + +You can feed the same function into multiple Call Function nodes to reuse logic throughout the graph, or even pass functions as arguments to build higher-order abstractions. Recursive calls are possible—feed the function object back into one of its own arguments—but always protect them with a termination condition to avoid infinite loops. + +## Working with Lists and Control Flow + +To implement loops or other flow control primitives, rely on the high-order nodes found under `duanyll/functional/high_order`. + +- Always convert ComfyUI Data Lists into `LIST` objects before sending them into functional nodes. `Data List` values force eager evaluation and will crash or hang your workflow. +- The table below summarizes the built-in high-order nodes; see `docs/node-reference.md` for parameter details and new nodes you can script yourself. + +| Node | Purpose | +| --- | --- | +| `Comap` | Apply multiple functions to the same input and collect the results. | +| `Fold` | Reduce a list from left to right (`functools.reduce`-style). | +| `Map` | Apply a function to every element in a `LIST`. | +| `MapIndexed` | Like Map, but passes the element index as the second argument. | +| `Nest` | Apply a function to an initial value `n` times. | +| `NestWhile` | Keep applying the function until the predicate returns `False`; behaves like a `while` loop. | +| `Select` | Filter a list based on a predicate function. | +| `TakeWhile` | Consume items from the start of a list while the predicate stays `True`. | + +![Mapping a function across a list](https://img.duanyll.com/img/1459c201.png) + +Implementation hint: the nodes rely on Python coroutines. Take a look at `comfyui_functional/high_level.py` and `CoroutineNodeBase` if you want to add your own high-order helpers. + +## Using Side Effects Safely + +Some situations call for side effects (logging, collecting intermediate results, pacing execution). The following nodes make that practical: + +- `Sow` / `Reap`: `Sow` pushes values into an implicit list, `Reap` returns everything collected so far. Chain the `signal` input/output with the surrounding nodes to enforce execution order. +- `Inspect`: Attach a raw node output to the `value` output on Inspect to probe intermediate values without forcing the entire branch to run at graph build time. Only runs when its `signal` output is consumed. +- `Sleep`: Adds a delay to make Inspect output easier to read in fast loops. + +![Collecting values in a loop](https://img.duanyll.com/img/f60c2562.png) + +Because these nodes may disable ComfyUI's cache for correctness, expect longer runtimes whenever side effects are involved. + +## Debugging Checklist + +- Function body outputs must terminate at `Function End`. Any loose connection that depends on Function Parameters will fire eagerly and break evaluation. +- Protect recursive or long-running loops with `max_depth` or `NestWhile` predicates. +- Use `Inspect` liberally to tap into conditionals. It prevents ComfyUI from rendering both branches of an `If/Else` when only one should run. + +## Example Workflows + +The `example_workflows/` directory ships with working graphs: + +- `functional-map.json` – demonstrates Map/MapIndexed on a numeric `LIST`. +- `functional-list-param.json` – shows how to juggle multiple parameters plus list inputs. +- `functional-nested-calls.json` – functions that call other functions (mirrors the blog demo). +- `functional-recursion.json` – guarded recursion via `Call Function`. +- `functional-sow-reap.json` & `functional-inspect.json` – Sow/Reap, Inspect, and Sleep in context. + +Open them via ComfyUI's load workflow dialog, study the wiring, and start adapting them for your projects. + +## Common Pitfalls + +- **"Prompt has no outputs"**: add a Call Function node or ensure something depends on the Call Function output. +- **Data List lock-ups**: convert everything to `LIST` using Basic Data Handling's conversion nodes before interacting with functional nodes. +- **Stale cached results**: the pack disables caching when it detects side effects. If you still see stale data, clear your ComfyUI cache manually. + +## Further Reading + +- Blog (Chinese): https://duanyll.com/2025/9/25/ComfyUI-Functional/ +- Node reference: [`docs/node-reference.md`](./node-reference.md)