fixed flickering

This commit is contained in:
Martino Ferrari
2026-09-01 09:51:07 +02:00
parent 0398434c61
commit 13fac79400
+51 -9
View File
@@ -1352,7 +1352,11 @@ function decimateAsync(cacheKey, t, v, threshold, gen) {
return result;
}
}
return cached || null; // stale entry, or nothing to draw yet
// Never hand out a stale decimation: drawing it (at its old timestamps) and
// then the fresh one a frame later is what makes the trace jump/shimmer on
// every push. Return null instead — the caller holds the previous render
// until the worker's fresh result lands (it flags the plot for redraw).
return null;
}
// Evict stale decimation cache entries for a plot (call when zoom range changes).
@@ -2467,8 +2471,12 @@ function buildLiveData(p) {
let dec;
if (cached) {
dec = cached;
} else if (p.uplot && p.uplot.data && p.uplot.data[0] && p.uplot.data[0].length) {
// Fresh decimation not ready yet — hold the previous render so the trace
// does not flicker between a stale decimation and the fresh one.
return p.uplot.data;
} else {
// Worker job submitted — sync fallback this frame so the plot isn't blank.
// First render: worker job submitted, nothing on screen yet — sync.
dec = decimate(masterRaw.t, masterRaw.v, targetPts);
}
sharedT = dec.t;
@@ -2541,7 +2549,14 @@ function buildTrigData(p) {
// same-length snapshot slice for the same range, so it is tagged separately.
const cacheKey = `${p.id}:${masterKey}:${t0.toFixed(6)}:${t1.toFixed(6)}:${masterRaw.t.length}:${usedFetched ? 'hi' : 'snap'}`;
const cachedDec = decimateAsync(cacheKey, masterRaw.t, masterRaw.v, targetPts);
const dec = cachedDec || decimate(masterRaw.t, masterRaw.v, targetPts);
let dec;
if (cachedDec) {
dec = cachedDec;
} else if (p.uplot && p.uplot.data && p.uplot.data[0] && p.uplot.data[0].length) {
return p.uplot.data; // hold the previous render until the fresh decimation lands
} else {
dec = decimate(masterRaw.t, masterRaw.v, targetPts);
}
// Convert absolute → relative seconds
const sharedT = new Float64Array(dec.t.length);
for (let i = 0; i < dec.t.length; i++) sharedT[i] = dec.t[i] - trigT;
@@ -2598,8 +2613,15 @@ function buildTrigFillData(p) {
masterV = masterRaw.v;
} else {
const cacheKey = `${p.id}:${masterKey}:trigfill`;
const dec = decimateAsync(cacheKey, masterRaw.t, masterRaw.v, targetPts, _dataGen) ||
decimate(masterRaw.t, masterRaw.v, targetPts);
const decd = decimateAsync(cacheKey, masterRaw.t, masterRaw.v, targetPts, _dataGen);
let dec;
if (decd) {
dec = decd;
} else if (p.uplot && p.uplot.data && p.uplot.data[0] && p.uplot.data[0].length) {
return p.uplot.data; // hold until the fresh decimation is ready
} else {
dec = decimate(masterRaw.t, masterRaw.v, targetPts);
}
sharedAbsT = dec.t;
masterV = dec.v;
}
@@ -3884,6 +3906,10 @@ function deletePlot(plotId) {
let _dbgTick = 0;
let _dataGen = 0; // incremented each time new data arrives
function renderDirtyPlots() {
// Schedule the next frame FIRST: an exception below must never kill the
// animation loop, or every plot would freeze until a page refresh.
requestAnimationFrame(renderDirtyPlots);
try {
// Compute global "now" once — shared by all rolling-window plots this frame.
const globalPlotNow = getGlobalNow();
@@ -3953,7 +3979,7 @@ function renderDirtyPlots() {
plots.forEach(p => {
if (!p.needsRedraw || !p.uplot || p.traces.length === 0) return;
try {
const inTrigModeNow = inTrigWindow();
// The x tick formatter and the cursor-sync group are baked into the uPlot
// options at construction. A plot built in live mode therefore keeps
@@ -3968,7 +3994,10 @@ function renderDirtyPlots() {
if (isRolling && _dataGen === p.lastDataGen && p.uplot.data && p.uplot.data[0] && p.uplot.data[0].length > 0) {
p.needsRedraw = false;
zoomGuard = true;
p.uplot.setScale('x', { min: globalPlotNow - windowSec, max: globalPlotNow });
// Use the same per-plot anchor as the rebuild path, so the rolling window
// does not jump when the frame switches between the two.
const plotNow = computePlotNow(p);
p.uplot.setScale('x', { min: plotNow - windowSec, max: plotNow });
zoomGuard = false;
return;
}
@@ -4005,12 +4034,25 @@ function renderDirtyPlots() {
p.uplot.setScale('x', { min: plotNow - windowSec, max: plotNow });
}
zoomGuard = false;
p._errCount = 0;
} catch (e) {
// One bad plot must not kill the whole render loop. Track consecutive
// failures and self-heal by rebuilding the uPlot instance.
p._errCount = (p._errCount || 0) + 1;
console.error(`[render] plot ${p.id}:`, e);
p.needsRedraw = true; // retry next frame
if (p._errCount >= 30) {
p._errCount = 0;
try { createUPlot(p); } catch (e2) { console.error(`[render] rebuild plot ${p.id}:`, e2); }
}
}
});
// Keep per-plot cursor value readouts in sync with live data.
if (cursors.mode === 'on') updatePlotCursorReadouts();
requestAnimationFrame(renderDirtyPlots);
} catch (e) {
console.error('[render]', e);
}
}