Files
90-RED-ComfyUI-LinkRouter/web/state.js
T

198 lines
6.2 KiB
JavaScript

// state.js — shared mutable state for LinkRouter.
//
// All cross-module variables live in `M`. Other modules import `M` and
// read/write via `M.xxx` — no ES live-binding reassignment issues.
// state.js depends only on router.js and app.js (no other local deps).
import { OrthoRouter } from "./router.js";
import { app } from "../../scripts/app.js";
// Defaults for the persisted floating-bar state (single source of truth —
// used both for the merge base and the corrupt-storage fallback).
const BAR_STATE_DEFAULTS = {
debug: false,
debugPanel: false,
lastFlowMode: "animated",
lastCornerMode: "per-line",
lastManualDragMode: "freeze-others",
recordSeconds: 30,
btnX: null,
btnY: null,
};
export const M = {
// --- localStorage key ---
LS_KEY: "linkrouter.state",
// --- floating-bar persisted state ---
barState: (() => {
try {
return Object.assign(
{ ...BAR_STATE_DEFAULTS },
JSON.parse(localStorage.getItem("linkrouter.state") || "{}"),
);
} catch {
return { ...BAR_STATE_DEFAULTS };
}
})(),
saveBarState() {
localStorage.setItem("linkrouter.state", JSON.stringify(this.barState));
},
// --- settings cache (filled initially by settings.js) ---
S: {},
ROUTER_KEYS: new Set([
"margin", "marginL", "marginR", "marginT", "marginB",
"marginMode", "bendPenalty",
]),
// --- execution state ---
running: false,
// --- routing state ---
router: null,
graphSig: "",
routeFastSig: "",
routeGraph: null,
routeResults: null,
routeBatch: null,
routeBatchRaf: 0,
_deferredGraphBuild: false,
routeCostByLink: new Map(),
routeCostByLinkMT: new Map(), // main-thread-measured costs only (pause race gate)
routeCostAverage: NaN,
prevRects: new Map(),
pathCache: new Map(), // linkId -> {ends, pts, sticky, segs, total}
failedRoutes: new Map(), // linkId -> {ends, fails, retryAt, bounds}
bounding: new Float32Array(4),
settleTimer: null,
_dragMovedIds: null,
_dragAdaptiveMode: null,
_dragHeavyActive: null,
_dragLastFastSig: "",
_dragHiddenLinkIds: new Set(),
_dragAffectedLinkIds: new Set(),
_dragPauseActive: false,
_dragPausePending: false,
_dragPauseQueue: null,
_dragPauseCleanupLinkIds: new Set(),
_dragPauseAttemptedLinkIds: new Set(),
_dragPauseCompletedLinkIds: new Set(),
_dragPauseRevealQueue: [], // [{linkId, cached}] worker-computed, awaiting per-frame reveal
_dragInterruptedBatch: false,
_lastDragSettle: null,
_pointerDown: false,
_nodeDragActive: false,
// --- animation state ---
animActive: false,
rafId: 0,
lastFrame: 0,
// --- overlay animation layer ---
_animLinks: [], // [{cached, color, alpha}] rebuilt by each drawAll
_animGapScale: 1, // adaptive marker-density gap multiplier (draw.js)
_overlayCanvas: null,
_overlayCtx: null,
_overlayFailed: false,
_overlayLoop: false,
// --- router worker (Phase D) ---
_worker: null, // Worker instance (owned by worker-client.js)
_workerFailed: false,
_workerJobRev: 0, // bumped on every dispatch/cancel; stale results die
_dragPauseWorker: null, // { jobRev, jobsById, graph, entries } held-pause queue dispatched to worker
// --- hover tracking ---
mouseClient: null, // {x, y} in client space
_hoverLinkId: null, // link id under the pointer (single-link hover; ui.js)
_hoverSlotLinkIds: null, // Set of link ids from the slot label/dot under the pointer (ui.js)
_hoverSlotNodeId: null, // id of the node owning the hovered slot (ui.js)
// --- floating bar DOM refs ---
uiBox: null, // DOM element
barRefs: null, // floating-bar controls used by refreshBar()
_barBaseW: null, // collapsed-bar pixel width; anchors left-growth (ui.js)
// --- failsafe notice (draw.js) ---
routeFailsafeReason: null, // set when drawAll disabled the plugin mid-session
// --- pure helpers (depend only on M.S) ---
currentMargin() {
if (this.S.marginMode === "uniform") return +this.S.margin || 16;
return {
l: +this.S.marginL || 16,
r: +this.S.marginR || 16,
t: +this.S.marginT || 16,
b: +this.S.marginB || 16,
};
},
resetRouter() {
if (this.routeBatchRaf) {
const cancel = globalThis.cancelAnimationFrame || globalThis.clearTimeout;
cancel?.(this.routeBatchRaf);
}
// A pending drag-settle belongs to the old router state: cancel the
// timer itself, not just the flag, so finishSettle cannot fire later.
if (this.settleTimer) {
clearTimeout(this.settleTimer);
this.settleTimer = null;
}
this.router = new OrthoRouter({
margin: this.currentMargin(),
bendPenalty: +this.S.bendPenalty || 40,
});
this.graphSig = "";
this.routeFastSig = "";
this.routeGraph = null;
this.routeResults = null;
this.routeBatch = null;
this.routeBatchRaf = 0;
this._deferredGraphBuild = false;
this.routeCostByLink.clear();
this.routeCostByLinkMT.clear();
this.routeCostAverage = NaN;
this.prevRects = new Map();
this.pathCache.clear();
this.failedRoutes.clear();
this._dragAdaptiveMode = null;
this._dragHeavyActive = null;
this._dragLastFastSig = "";
this._dragMovedIds = null;
this._dragHiddenLinkIds.clear();
this._dragAffectedLinkIds.clear();
this._dragPauseActive = false;
this._dragPausePending = false;
this._dragPauseQueue = null;
this._dragPauseCleanupLinkIds.clear();
this._dragPauseAttemptedLinkIds.clear();
this._dragPauseCompletedLinkIds.clear();
this._dragPauseRevealQueue.length = 0;
this._dragPauseWorker = null;
this._dragInterruptedBatch = false;
this._lastDragSettle = null;
this._nodeDragActive = false;
this._animLinks.length = 0;
// Orphan any in-flight worker batch: bump the rev so its results are
// discarded, and tell the worker to stop (worker-client owns the object).
this._workerJobRev++;
try {
this._worker?.postMessage({ type: "cancel" });
} catch {}
app.canvas?.setDirty(true, true);
},
animEnabledNow() {
if (this.running && this.S.animDuringRun === "off") return false;
return true;
},
currentFPS() {
if (this.running && this.S.animDuringRun === "low") return 10;
return +this.S.animFPS || 30;
},
};