fixed flickering
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user