Three things, all about a run telling you what it is doing.
1. The second pass is now visible. ComfyUI's progress channel carries only
(value, max), so a chain sampling some scenes twice was one anonymous bar.
The sampler publishes a live label ("scene 2/3 · pass 2 of 2") through the
new run_phase module; /api/progress hands it to the editor, which appends it
to the readout, and pass 2 also announces itself on the console the moment it
starts rather than only in the after-the-fact run report.
run_phase keeps its state on `sys`, not in module globals: the writer reaches
it package-relative and the reader imports FunPack modules top-level by name,
so they can hold different module objects and the label would never arrive.
2. The progress bar total now counts both passes. It only ever counted the main
schedule, so with a second pass on it overflowed during scene 1 and jumped
backwards at scene 2, whose offset is a multiple of the per-scene stride.
3. context_windows was unusable. The knob offered "uniform_standard" etc.;
core's names are the other way round ("standard_uniform"), so every schedule
except "batched" raised ValueError out of the sampler and FAILED THE RENDER.
Core's names are the choices now, the old spellings are aliased so saved
projects keep working, and an unknown name is refused with a reason naming
what core accepts instead of crashing.
Same call had a second, quieter break: a keyword core's handler doesn't take
(latent_retain_index_list is absent on some builds, including this Mac's)
raised TypeError, which was reported as "ComfyUI core too old" — silently
switching off a feature core fully supports. Extras are dropped now, with a
note naming the one knob that stops working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""run_phase — the live "what is the sampler doing" label behind the progress readout.
|
|
|
|
The one invariant that actually breaks in production: the WRITER (samplers.py) reaches this
|
|
module as a package-relative import and the READER (movie_editor's bridge) imports FunPack
|
|
modules top-level by name, so the two can hold different module objects. State in module
|
|
globals would be two separate copies and the label would never arrive. It lives on `sys`
|
|
for exactly that reason, and this pins it down.
|
|
"""
|
|
import importlib.util
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import run_phase # noqa: E402
|
|
|
|
|
|
def _second_import():
|
|
"""A second, independent module object for the same file — what the other import path
|
|
produces at runtime."""
|
|
spec = importlib.util.spec_from_file_location("other_pkg.run_phase", ROOT / "run_phase.py")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def test_both_import_paths_see_the_same_label():
|
|
other = _second_import()
|
|
run_phase.set_phase("scene 2/3 · pass 2 of 2")
|
|
assert other.current()["label"] == "scene 2/3 · pass 2 of 2"
|
|
other.clear()
|
|
assert run_phase.current()["label"] == ""
|
|
|
|
|
|
def test_seq_only_ever_increases():
|
|
"""A poller uses it to tell "still the same phase" from "this phase came round again",
|
|
so it must never reset — not even when the label is cleared."""
|
|
before = run_phase.current()["seq"]
|
|
run_phase.set_phase("a")
|
|
run_phase.set_phase("a")
|
|
run_phase.clear()
|
|
assert run_phase.current()["seq"] == before + 3
|
|
|
|
|
|
def test_a_bad_label_never_raises():
|
|
"""This is a readout on the sampling path: it must not be able to fail a render."""
|
|
run_phase.set_phase(None)
|
|
assert run_phase.current()["label"] == ""
|
|
run_phase.set_phase(12)
|
|
assert run_phase.current()["label"] == "12"
|
|
run_phase.clear()
|