Replace 500ms title polling with canvas.setDirty hook + relevant-key refresh (fix flicker)

This commit is contained in:
solidlime
2026-08-02 14:37:43 +09:00
parent f0856213ce
commit 113679fdde
+64 -35
View File
@@ -74,41 +74,63 @@ function applyValue(targetNode, mode, value) {
app.graph.setDirtyCanvas(true, true);
}
// ── rename polling (title hash) ─────────────────────────
// The new frontend renames nodes by assigning node.title directly (no
// graph.change() / onPropertyChanged fired), so renames are detected by
// polling a title hash (cg-use-everywhere approach). One shared 500ms
// interval: started when the first panel installs its hooks, stopped when
// the last panel is removed.
// ── canvas.setDirty hook (rename detection) ──────────────
// The new frontend renames nodes by assigning node.title directly and only
// calls canvas.setDirty — neither graph.change() nor onPropertyChanged fire.
// Wrapping canvas.setDirty (installed once) is the only observable signal
// for label changes; panels then re-render only when their own relevant key
// actually changed. Add/remove are already covered by graph hooks.
const panels = new Set();
let titlesPollTimer = null;
let lastTitlesKey = "";
let panelsCheckTimer = null;
const PANELS_CHECK_MS = 150;
function titlesKey() {
return (app.graph?._nodes || [])
.map((n) => (n.title || "") + "\u0000" + (n.type || ""))
.join("|");
function relevantKey(panel) {
const mode = getPanelMode(panel);
const names = resolveTargets(panel)
.map((t) => itemName(panel, t).toLowerCase())
.sort();
return (panel.title || "") + "\u0000" + mode + "\u0000" + names.join("\u0000");
}
function ensurePolling() {
if (titlesPollTimer == null && panels.size > 0) {
lastTitlesKey = titlesKey();
titlesPollTimer = setInterval(() => {
const k = titlesKey();
if (k !== lastTitlesKey) {
lastTitlesKey = k;
panels.forEach((p) => {
if (typeof p.__scheduleRefresh === "function") p.__scheduleRefresh();
});
}
}, 500);
}
function checkPanels() {
panels.forEach((p) => {
if (typeof p.__scheduleRefresh !== "function") return;
const k = relevantKey(p);
if (k !== p.__mbpLastKey) {
p.__mbpLastKey = k;
p.__scheduleRefresh();
}
});
}
function stopPollingIfEmpty() {
if (panels.size === 0 && titlesPollTimer != null) {
clearInterval(titlesPollTimer);
titlesPollTimer = null;
function schedulePanelsCheck() {
if (panelsCheckTimer) clearTimeout(panelsCheckTimer);
panelsCheckTimer = setTimeout(() => {
panelsCheckTimer = null;
checkPanels();
}, PANELS_CHECK_MS);
}
function mbpSetDirtyWrapper(fg, bg) {
const r = this.__mbpPrevSetDirty.call(this, fg, bg);
schedulePanelsCheck();
return r;
}
function installCanvasHook(canvas) {
if (!canvas || canvas.__mbpHooked) return;
canvas.__mbpPrevSetDirty = canvas.setDirty;
canvas.setDirty = mbpSetDirtyWrapper;
canvas.__mbpHooked = true;
}
function uninstallCanvasHook() {
if (panels.size > 0) return;
const canvas = app.canvas;
if (canvas && canvas.__mbpHooked && canvas.setDirty === mbpSetDirtyWrapper) {
canvas.setDirty = canvas.__mbpPrevSetDirty;
canvas.__mbpPrevSetDirty = null;
canvas.__mbpHooked = false;
}
}
@@ -262,8 +284,12 @@ function buildPanel(panelNode) {
const rows = listEl.children.length;
const h = rows * ROW_HEIGHT + HEADER_PAD;
rowsHeight = Math.max(h + HEADER_PAD, 60);
panelNode.size = [panelNode.size[0], rowsHeight];
panelNode.setDirtyCanvas(true, true);
// Skip resize/redraw when the height is unchanged (avoids flicker from
// redundant setDirtyCanvas on every render pass).
if (panelNode.size[1] !== rowsHeight) {
panelNode.size = [panelNode.size[0], rowsHeight];
panelNode.setDirtyCanvas(true, true);
}
}
// ── toggle element factory ────────────────────────────
@@ -299,8 +325,8 @@ function buildPanel(panelNode) {
// ── event hooks ───────────────────────────────────────
// Expose scheduleRefresh so the module-level rename poller can trigger
// refreshes on all live panels.
// Expose scheduleRefresh so checkPanels() (driven by the canvas.setDirty
// hook) can trigger refreshes on this panel.
panelNode.__scheduleRefresh = scheduleRefresh;
// nodeCreated runs inside the ComfyNode constructor, where node.graph is
@@ -341,7 +367,7 @@ function buildPanel(panelNode) {
panelNode.onAdded = function (graph) {
installGraphHooks(graph || panelNode.graph);
panels.add(panelNode);
ensurePolling();
installCanvasHook(app.canvas);
if (origOnAdded) origOnAdded.call(this, graph);
};
@@ -357,6 +383,8 @@ function buildPanel(panelNode) {
// Initial render
render();
// Baseline for change detection so the first relevant change re-renders.
panelNode.__mbpLastKey = relevantKey(panelNode);
// Cleanup on removal
const origOnRemoved = panelNode.onRemoved;
@@ -364,7 +392,8 @@ function buildPanel(panelNode) {
uninstallGraphHooks();
panels.delete(panelNode);
panelNode.__scheduleRefresh = null;
stopPollingIfEmpty();
panelNode.__mbpLastKey = null;
uninstallCanvasHook();
if (origOnRemoved) origOnRemoved.call(this);
};
}