Browse Source

The Fix:

The new loop iterates through the data and draws rectangles directly to the canvas. It changes ctx.fillStyle more often (once per column), but this cost is negligible  to the cost of allocating and collecting memory 60 times a second. This should resolve the heap memory issue you observed.
refactor/sync-centralize
RUSHIL AMBARISH KADU 6 months ago
parent
commit
d79f7c51c8
  1. 57
      steps/src/dom.js
  2. 78
      steps/src/drawUtils.js

57
steps/src/dom.js

@ -252,6 +252,7 @@ function getCurrentColorMode() {
// Cache for DOM elements to avoid querySelector/getElementById every frame
let overlayCache = null;
let videoOverlayCache = null;
// Cache for conditional rendering
let lastDrawnFrame = -1;
@ -381,8 +382,10 @@ export function updatePersistentOverlays(currentMediaTime) {
ctx.clearRect(0, 0, w, h);
// --- 1. Batching Phase ---
const batches = {};
// --- Optimization: Immediate Mode Drawing (No Allocations) ---
// Instead of batching into objects, we draw directly.
// We iterate through columns. To minimize state changes, we could pre-sort,
// but simply drawing column-by-column is fast enough and avoids GC.
for (let offset = -centerCol; offset < centerCol; offset++) {
const targetFrameIndex = appState.currentFrame + offset;
@ -396,30 +399,22 @@ export function updatePersistentOverlays(currentMediaTime) {
const numBlocks = Math.min(10, Math.max(1, Math.round(ift / msPerBlock)));
const color = getTimingColor(ift);
if (!batches[color]) batches[color] = [];
ctx.fillStyle = color;
ctx.beginPath();
for (let d = 0; d < numBlocks; d++) {
const y = h - (d * (blockSize + vGap)) - 3;
batches[color].push({x, y});
ctx.rect(x, y, blockSize, blockSize);
}
ctx.fill();
} else {
// Placeholder blocks
const color = "rgba(255, 255, 255, 0.1)";
if (!batches[color]) batches[color] = [];
ctx.fillStyle = "rgba(255, 255, 255, 0.1)";
ctx.beginPath();
const y = h - 3;
batches[color].push({x, y});
}
}
// --- 2. Drawing Phase ---
// Draw all blocks of the same color in one pass
for (const [color, points] of Object.entries(batches)) {
ctx.fillStyle = color;
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x, p.y, blockSize, blockSize);
ctx.rect(x, y, blockSize, blockSize);
ctx.fill();
}
ctx.fill();
}
// Draw Center Indicator (Triangle at column 60)
@ -447,11 +442,25 @@ export function updatePersistentOverlays(currentMediaTime) {
timeDisplay += ` / ${videoPlayer.duration.toFixed(2)}s`;
}
videoInfoOverlay.innerHTML = `
Frame: ${videoFrame}
| ${timeDisplay}
| Abs Time: ${formatUTCTime(absVideoTime)}
`;
// --- OPTIMIZATION: Video Overlay Caching ---
if (!videoOverlayCache) {
videoInfoOverlay.innerHTML = `
Frame: <span id="ov-vid-frame"></span>
| <span id="ov-vid-time"></span>
| Abs Time: <span id="ov-vid-abs"></span>
`;
videoOverlayCache = {
frame: document.getElementById("ov-vid-frame"),
time: document.getElementById("ov-vid-time"),
abs: document.getElementById("ov-vid-abs")
};
}
if (videoOverlayCache) {
videoOverlayCache.frame.textContent = videoFrame;
videoOverlayCache.time.textContent = timeDisplay;
videoOverlayCache.abs.textContent = formatUTCTime(absVideoTime);
}
}
const customTtcInputs = [

78
steps/src/drawUtils.js

@ -437,18 +437,18 @@ export function drawTrackMarkers(p, plotScales) {
const localStationaryColor = stationaryColor(p);
const localMovingColor = movingColor(p);
// Optimization: Batch drawing commands
// We collect all text labels to draw them in a single pass at the end.
// This avoids switching between stroke/fill and push/pop for every track.
const textLabels = [];
p.push();
p.strokeWeight(2);
for (const track of appState.vizData.tracks) {
// --- START: Add the Same Safeguard Here ---
// This robust check ensures the track and its historyLog are valid before use.
if (toggleConfirmedOnly.checked && track.isConfirmed === false) {
continue;
}
if (!track || !track.historyLog || !Array.isArray(track.historyLog)) {
// We don't need to log a warning here again, as drawTrajectories already did.
// We can just safely skip this malformed track.
continue;
}
// --- END: Add the Same Safeguard Here ---
if (toggleConfirmedOnly.checked && track.isConfirmed === false) continue;
// Robust check for malformed tracks (same as drawTrajectories)
if (!track || !track.historyLog || !Array.isArray(track.historyLog)) continue;
const log = track.historyLog.find(
(log) => log.frameIdx === appState.currentFrame
@ -460,63 +460,79 @@ export function drawTrackMarkers(p, plotScales) {
const size = 5;
const x = pos[0] * plotScales.plotScaleX;
const y = pos[1] * plotScales.plotScaleY;
let velocityColor = p.color(255, 0, 255, 200);
p.push();
p.strokeWeight(2);
// --- Draw Marker Shape ---
if (useStationary && log.isStationary === true) {
p.stroke(localStationaryColor);
p.noFill();
p.rectMode(p.CENTER);
p.square(x, y, size * 1.5);
velocityColor = localStationaryColor;
} else {
let markerColor = p.color(0, 0, 255);
if (useStationary && log.isStationary === false) {
markerColor = localMovingColor;
velocityColor = localMovingColor;
}
p.stroke(markerColor);
p.line(x - size, y, x + size, y);
p.line(x, y - size, x, y + size);
}
p.pop();
// --- Draw Velocity Vector & Collect Text ---
if (
showDetails &&
log.predictedVelocity &&
log.predictedVelocity[0] !== null
) {
const [vx, vy] = log.predictedVelocity;
// Draw velocity line immediately (shares stroke context)
if (log.isStationary === false) {
p.push();
p.stroke(velocityColor);
p.strokeWeight(2);
p.line(
// Determine color again (optimization: could be refactored to avoid recalc)
let velocityColor = p.color(255, 0, 255, 200);
if (useStationary) velocityColor = localMovingColor;
p.stroke(velocityColor);
p.line(
x,
y,
(pos[0] + vx) * plotScales.plotScaleX,
(pos[1] + vy) * plotScales.plotScaleY
);
p.pop();
}
const speed = (p.sqrt(vx * vx + vy * vy) * 3.6).toFixed(1);
// Defer Text Drawing
const speed = (Math.sqrt(vx * vx + vy * vy) * 3.6).toFixed(1);
const ttc =
log.ttc !== null && isFinite(log.ttc) && log.ttc < 100
? `TTC: ${log.ttc.toFixed(1)}s`
: "";
const text = `ID: ${track.id} | ${speed} km/h\n${ttc}`;
p.push();
p.fill(textColor);
p.noStroke();
p.scale(1, -1);
p.textSize(12);
p.text(text, x + 10, -y);
p.pop();
textLabels.push({ x, y, text });
}
}
}
}
p.pop(); // End shape drawing context
// --- Batch Draw Text ---
if (textLabels.length > 0) {
p.push();
p.fill(textColor);
p.noStroke();
p.textSize(12);
// Set alignment once
// Note: we handle the flip manually
for (const label of textLabels) {
p.push();
p.translate(label.x + 10, label.y);
p.scale(1, -1); // Flip text back up
p.text(label.text, 0, 0);
p.pop();
}
p.pop();
}
}

Loading…
Cancel
Save