Major features:
- Adaptive drag mode with 5 fixed modes (Normal, Freeze+Check, Freeze, Hide, Heavy Deferred)
- Heavy Deferred for lag-free dragging in large workflows (250+ nodes)
- Three drag phases: moving → held → released, with priority-based re-routing during pause
- Node hard-obstacle avoidance — links can never pass through nodes
Performance:
- Progressive routing with frame-budgeted progressive batches
- Fast graph signature for geometry-change detection
- OVG rebuild deferred during large drags
- Collision re-check limited to affected links only
Fixes:
- Hover no longer triggers full-workflow redraw
- Krea2 / Ultimate SD Upscale link disappearance during drag
- Stale sticky paths now validated before reuse
- Drag-endpoint dogleg for straight-line links
New modules: adaptive-policy, drag-policy, draw-policy, profiler, progressive, ui-policy
Tests: 39 automated tests covering routing, adaptive, drag, profiler
Floating bar: Adaptive 🧠 toggle, 5 fixed-mode buttons, debug row, Nodes 2.0 switch
45 lines
1.5 KiB
JavaScript
45 lines
1.5 KiB
JavaScript
// Pure helpers for spreading expensive connector searches over multiple frames.
|
|
|
|
// Small/medium route sets are cheaper and look much better when they appear
|
|
// together. Progressive reveal is reserved for genuinely large updates.
|
|
export const PROGRESSIVE_ROUTE_THRESHOLD = 16;
|
|
|
|
export function shouldProgressivelyRoute(dragging, forceSync, jobCount) {
|
|
return !dragging && !forceSync && jobCount >= PROGRESSIVE_ROUTE_THRESHOLD;
|
|
}
|
|
|
|
export function progressiveItemLimit(total, percent) {
|
|
const pct = Math.max(1, Math.min(100, +percent || 10));
|
|
return Math.max(1, Math.ceil(total * pct / 100));
|
|
}
|
|
|
|
export function processRouteSlice(batch, routeOne, options = {}) {
|
|
const now = options.now || (() => performance.now());
|
|
const maxItems = options.maxItems ?? Infinity;
|
|
const budgetMs = options.budgetMs ?? Infinity;
|
|
const started = now();
|
|
let processed = 0;
|
|
|
|
while (batch.index < batch.jobs.length && processed < maxItems) {
|
|
const job = batch.jobs[batch.index++];
|
|
const result = routeOne(job);
|
|
if (result) batch.resultsById.set(job.entry.link.id, result);
|
|
processed++;
|
|
if (processed > 0 && now() - started >= budgetMs) break;
|
|
}
|
|
return {
|
|
processed,
|
|
done: batch.index >= batch.jobs.length,
|
|
remaining: batch.jobs.length - batch.index,
|
|
};
|
|
}
|
|
|
|
export function orderedRouteResults(entries, resultsById) {
|
|
const out = [];
|
|
for (const entry of entries) {
|
|
const result = resultsById.get(entry.link.id);
|
|
if (result) out.push(result);
|
|
}
|
|
return out;
|
|
}
|