Files
olivv-cs-ComfyUI-FunPack/run_phase.py
T
digital-garbageandClaude Opus 5 7681c599a0 progress: say which scene and which pass is running; fix context_window_schedule
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>
2026-07-31 17:57:21 +03:00

60 lines
2.5 KiB
Python

"""What the sampler is doing RIGHT NOW, as a short label a UI can poll.
ComfyUI's progress channel carries only (value, max) — no text — so a chain that samples
several scenes, some of them twice, reads as one undifferentiated bar: you can watch the
number climb without knowing which scene it belongs to or whether the second pass has
started. The Chain Sampler publishes a label here as it moves through the run, and the
Movie Editor's ``/api/progress`` hands it to the UI alongside the numbers.
**The state lives on `sys`, not in this module's globals, and that is load-bearing.** The
writer (samplers.py) reaches this file as a package-relative import, so it gets
``ComfyUI-FunPack.run_phase``; the reader (movie_editor/backend/bridge.py) imports FunPack
modules top-level by name after putting the root on sys.path, so it would get a SECOND,
separate ``run_phase`` module object — two copies, two sets of globals, and a label that
never arrives. Hanging the dict off ``sys`` (unambiguously one object per process) makes
both import paths share it. Stateless helpers like `conditioning` do not care which copy
they are; anything holding mutable state across that boundary does.
Everything here is BEST-EFFORT and never affects sampling: it is a live readout, not state
anything is derived from. Nothing reads it back to make a decision, so a label left over
from a crashed run is cosmetic — the next run overwrites it and ``clear()`` empties it —
and it is not the kind of cross-request cache that would violate "the UI is the only source
of truth".
"""
import sys
_SYS_KEY = "_funpack_run_phase"
def _state() -> dict:
st = getattr(sys, _SYS_KEY, None)
if not isinstance(st, dict):
st = {"label": "", "seq": 0}
setattr(sys, _SYS_KEY, st)
return st
def set_phase(label: str) -> None:
"""Publish the current phase, e.g. "scene 2/3 · pass 2 of 2"."""
try:
st = _state()
st["label"] = str(label or "")
st["seq"] += 1
except Exception: # noqa: BLE001 — a readout must never break a render
pass
def clear() -> None:
"""Nothing is sampling. Call from a finally, so an interrupt doesn't leave a label up."""
set_phase("")
def current() -> dict:
"""{"label": str, "seq": int}. `seq` only ever increases, so a poller can tell "still
the same phase" from "this phase came round again"."""
try:
st = _state()
return {"label": st["label"], "seq": st["seq"]}
except Exception: # noqa: BLE001
return {"label": "", "seq": 0}