Replace 500ms title polling with canvas.setDirty hook + relevant-key refresh (fix flicker)
This commit is contained in:
+64
-35
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user