Files
90-RED 734ce4173f v1.4.3 — faster progressive reveal + fix links vanishing after drag
Worker prewarm, viewport-first stable batches, and adaptive reveal pacing
(default on, clamp 6–14ms). On drag settle force OVG rebuild and never drop
the last legal path when route/stretch fail.
2026-07-30 10:44:53 +08:00

98 lines
3.7 KiB
JavaScript

// ComfyUI-LinkRouter — object-avoiding orthogonal link routing.
//
// Links are routed with an Orthogonal Visibility Graph + A* search
// (Wybrow et al., GD'09 — the libavoid algorithm).
//
// All options live in ComfyUI Settings under the LinkRouter category.
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { M } from "./state.js";
import { registerSettings, setRefreshBar } from "./settings.js";
import { drawAll } from "./draw.js";
import { buildUI, refreshBar, watchHover, linksHidden } from "./ui.js";
import { cancelDragPauseWorker } from "./routing.js";
import { prewarmWorker } from "./worker-client.js";
// Wire the refresh-bar callback (settings.js => ui.js without circular import)
setRefreshBar(refreshBar);
// --- execution-state watcher ---
function watchExecution() {
try {
api.addEventListener("execution_start", () => { M.running = true; });
const done = () => { M.running = false; };
api.addEventListener("execution_success", done);
api.addEventListener("execution_error", done);
api.addEventListener("execution_interrupted", done);
api.addEventListener("status", (ev) => {
const q = ev?.detail?.exec_info?.queue_remaining;
if (q === 0) M.running = false;
});
} catch (e) {
console.warn("[LinkRouter] execution watch unavailable", e);
}
}
// --- hook into ComfyUI ---
app.registerExtension({
name: "LinkRouter",
setup() {
registerSettings();
buildUI();
watchHover();
watchExecution();
// Settings are loaded by now: spin up the background worker and warm its
// router JIT with a tiny synthetic batch (no-op when worker routing is
// off/unavailable). See prewarmWorker in worker-client.js.
prewarmWorker();
// Capture-phase tracking still sees gestures captured by Nodes 2.0.
const beginPointer = () => {
// A new gesture orphans the previous gesture's in-flight pause batch:
// its late results would pass the jobRev check into the new reveal
// queue, and a stale _dragPauseWorker would block the next dispatch.
cancelDragPauseWorker();
M._pointerDown = true;
M._nodeDragActive = false;
M._dragAdaptiveMode = null;
M._dragHeavyActive = null;
M._dragLastFastSig = M.routeFastSig || "";
M._dragHiddenLinkIds.clear();
M._dragAffectedLinkIds.clear();
M._dragPauseActive = false;
M._dragPausePending = false;
M._dragPauseQueue = null;
M._dragPauseCleanupLinkIds.clear();
M._dragPauseAttemptedLinkIds.clear();
M._dragPauseCompletedLinkIds.clear();
M._dragPauseRevealQueue.length = 0;
M._dragInterruptedBatch = false;
};
const releasePointer = () => { M._pointerDown = false; };
window.addEventListener("pointerdown", beginPointer, true);
window.addEventListener("pointermove", (ev) => {
// Pointer capture can occasionally hide the original down event from an
// extension, but the held-button state remains reliable.
if (ev.buttons & 1) M._pointerDown = true;
}, true);
window.addEventListener("pointerup", releasePointer, true);
window.addEventListener("pointercancel", releasePointer, true);
window.addEventListener("blur", releasePointer);
const proto = LGraphCanvas.prototype;
const original = proto.drawConnections;
proto.drawConnections = function (ctx) {
// Mirror the native contract even on early exits: a frame that draws
// no links must leave an empty hit-test set (LGraphCanvas.ts:5977).
if (linksHidden(this)) {
this.renderedPaths?.clear?.();
return; // official "hide links" wins
}
if (!M.S.enabled) return original.call(this, ctx);
if (drawAll(this, ctx) === false) return original.call(this, ctx);
};
},
});