Files
90-RED-ComfyUI-LinkRouter/web/drag-policy.js
T
90-RED 3474927aee v1.3.0 — Web Worker routing, profiler disk export, stretch extraction
- Web Worker routing — A* search runs in a background Worker thread,
  keeping the UI responsive during large batch routes
- Worker watchdog — auto-falls back to main-thread routing on any failure
- Worker held-pause — drag-pause re-routes also run in worker
- stretch.js — extracted sticky-path logic, shared by main thread and worker
- Profiler disk export — POST /linkrouter/write_log endpoint saves reports
- Chunked worker processing — 8ms macrotask slices for cancel responsiveness
- New settings: Background worker routing, Held-pause in worker
2026-07-19 06:36:24 +08:00

120 lines
4.1 KiB
JavaScript

// Drag-settle is only for interactive geometry changes. Vue/DOM nodes may
// report new sizes while panning or zooming; treating those as a drag causes a
// second, unnecessary route pass when sticky paths are released.
export function shouldUseDragSettle(pointerDown, geometry, canvas) {
if (!pointerDown) return false;
return (geometry?.positionChanges || 0) > 0 || !!canvas?.resizing_node;
}
// Sticky stretching is only safe when a link endpoint moved. If an unrelated
// link was hit by the dragged node, stretching would preserve the same old
// path through the node and suppress the live avoidance re-route.
export function shouldStretchDragPath({
dragging,
hasCachedPath,
endsMoved,
hitDirty,
}) {
return !!(dragging && hasCachedPath && endsMoved && !hitDirty);
}
export function shouldRouteHeldCollision(effectiveMode, pauseActive, collides, attempted = false) {
return !!(
pauseActive &&
collides &&
!attempted &&
(
effectiveMode === "freeze-others" ||
effectiveMode === "freeze-others-strict" ||
effectiveMode === "hide-self"
)
);
}
export function shouldFreezeHiddenModeLink(isDragged, hasCachedPath, heldCollision) {
return !!(!isDragged && hasCachedPath && !heldCollision);
}
export function shouldQueueIdleCleanup(effectiveMode, affected, primary, hasCachedPath) {
return !!(
effectiveMode === "freeze-others" &&
affected &&
!primary &&
hasCachedPath
);
}
export function pathBounds(points) {
if (!points?.length) return null;
let x = Infinity, y = Infinity, x2 = -Infinity, y2 = -Infinity;
for (const p of points) {
x = Math.min(x, p.x);
y = Math.min(y, p.y);
x2 = Math.max(x2, p.x);
y2 = Math.max(y2, p.y);
}
return { x, y, x2, y2 };
}
function intersects(a, b) {
return !!(a && b && a.x < b.x2 && a.x2 > b.x && a.y < b.y2 && a.y2 > b.y);
}
function distanceFromViewport(bounds, viewport) {
if (!bounds || !viewport) return Infinity;
const dx = bounds.x2 < viewport.x
? viewport.x - bounds.x2
: bounds.x > viewport.x2
? bounds.x - viewport.x2
: 0;
const dy = bounds.y2 < viewport.y
? viewport.y - bounds.y2
: bounds.y > viewport.y2
? bounds.y - viewport.y2
: 0;
return dx + dy;
}
// Worker-computed held-pause paths are revealed the very next frame after
// arrival, all at once. Pacing made sense when compute lived on the main
// thread (the one-by-one appearance doubled as progress feedback), but the
// worker now delivers in clumps spread across the whole pause — measured
// pauses last ~100-130ms while 17 paced reveals need another ~280ms, so the
// tail links were discarded on resume without ever showing. A reveal is only
// a couple of Map writes and can never stall the main thread, so the whole
// pending queue drains in a single frame.
export function pauseRevealCount(remaining) {
if (!(remaining > 0)) return 0;
return remaining;
}
// Held-pause race: while the worker chews through the whole pause queue,
// the main thread may immediately route links predicted to be cheap, so
// short pauses get the old one-by-one feedback. Unknown links use the
// session-average fallback (usually cheap) — a mis-predicted link costs one
// frame at most, and its true cost is learned for next time.
export function shouldRacePauseLink(costMs, fallbackMs, maxMs) {
return (costMs === undefined ? fallbackMs : costMs) < maxMs;
}
// Freeze this order when a held pause begins: direct links first, then links
// visible in the current viewport, then off-screen links expanding outward.
export function orderHeldRouteCandidates(candidates, viewport) {
return candidates
.map((candidate, index) => {
const visible = intersects(candidate.bounds, viewport);
return {
...candidate,
_priority: candidate.direct ? 0 : visible ? 1 : 2,
_distance: candidate.direct ? 0 : distanceFromViewport(candidate.bounds, viewport),
_index: index,
};
})
.sort((a, b) =>
a._priority - b._priority ||
a._distance - b._distance ||
a._index - b._index
)
.map(({ _priority, _distance, _index, ...candidate }) => candidate);
}