From 72f6e67127623e70ee946d51c638ab94cf37596e Mon Sep 17 00:00:00 2001 From: Avatarsia Date: Mon, 6 Apr 2026 05:23:09 +0200 Subject: [PATCH] perf: time-based yield reduces background tab overhead by ~95% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace fixed-interval yields (every 4096 iterations) with time-based yields (every ~100ms of wall time). In foreground tabs this means ~10 yields per second instead of ~50-200, with identical UI responsiveness. In background tabs where setTimeout(0) is throttled to ~1s, this reduces overhead from ~200 wasted seconds to ~10 — the export runs nearly as fast in the background as in the foreground. Addresses #2 (background tab resource allocation). --- js/decimation.js | 21 +++++++++++++++------ js/subdivision.js | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/js/decimation.js b/js/decimation.js index cb965ea..2def897 100644 --- a/js/decimation.js +++ b/js/decimation.js @@ -46,8 +46,17 @@ const FLIP_DOT_SQ = FLIP_DOT * FLIP_DOT; const CREASE_COS = 0.5; // cos 60° — edges sharper than this are treated as creases const CREASE_WEIGHT = 1e4; // quadric penalty weight for crease edges -// Yield to browser for UI responsiveness during long-running decimation. -// setTimeout(0) guarantees a real rendering frame between iterations. +// Time-based yield: only yield every ~100ms of wall time instead of every N iterations. +// In foreground tabs setTimeout(0) costs ~4ms; in background tabs it's throttled to ~1s. +// By yielding based on elapsed time we get ~10 yields per second in foreground (smooth progress) +// and minimal extra delay in background (~10 yields × 1s = ~10s overhead instead of ~200s). +let _lastYieldTime = 0; +function _shouldYield() { + const now = performance.now(); + if (now - _lastYieldTime < 100) return false; + _lastYieldTime = now; + return true; +} function _yieldFrame() { return new Promise(r => setTimeout(r, 0)); } @@ -109,10 +118,10 @@ export async function decimate(geometry, targetTriangles, onProgress) { const idx = heap.pop(); if (idx < 0) break; - // Yield periodically based on total iterations (including rejections) - // to keep the UI responsive. Critical for flat / low-displacement - // surfaces where most collapses are rejected by the safety guards. - if ((++iterations & 4095) === 0) { + // Yield based on elapsed wall time (~every 100ms) instead of fixed iteration count. + // Drastically reduces overhead in background tabs where setTimeout is throttled to 1s. + ++iterations; + if (_shouldYield()) { await _yieldFrame(); if (onProgress) { const p = Math.min(1, (initFaces - activeFaces) / toRemove); diff --git a/js/subdivision.js b/js/subdivision.js index 1889c33..0fbca4c 100644 --- a/js/subdivision.js +++ b/js/subdivision.js @@ -84,6 +84,7 @@ export async function subdivide(geometry, maxEdgeLength, onProgress, faceWeights const newTriCount = newIndices.length / 3; if (onProgress) onProgress(Math.min(0.95, (iter + 1) / maxIterations), newTriCount, longestEdge); + // Yield once per subdivision pass (not per iteration) — keeps background tabs fast await new Promise(r => setTimeout(r, 0)); if (!changed || safetyCapHit) break; }