Loading src/render_worker.ts +80 −81 Original line number Diff line number Diff line Loading @@ -58,45 +58,34 @@ parentPort?.on("message", (msg: Message) => { const start = Date.now(); const framesToGenerate = (msg.end - msg.start) / msg.step; // Create one canvas that persists across all frames in this worker. // Subsequent frames draw on the same canvas — no PNG decode needed. const canvas = Canvas.createCanvas(OUTPUT_SIZE[0], OUTPUT_SIZE[1]); const ctx = canvas.getContext("2d"); const pixelScale = [ OUTPUT_SIZE[0] / CANVAS_SIZE[0], OUTPUT_SIZE[1] / CANVAS_SIZE[1], ]; let index = msg.frameStart; let needsInit = true; for (let time = msg.start; time < msg.end; time += msg.step) { const pixels = await getPixelsAsLog(time, time + msg.step); log("generating frame " + index); await generateFrame(index, pixels); parentPort?.postMessage({ type: "progress", progress: (index - msg.frameStart) / framesToGenerate, }); index++; } log(`Finished in ${((Date.now() - start) / 1000).toFixed(2)} seconds`); parentPort?.postMessage({ type: "finished" }); }); break; } } }); const generateFrame = async (frameId: number, pixels: Pixel[]) => { const start = Date.now(); const canvas = Canvas.createCanvas(OUTPUT_SIZE[0], OUTPUT_SIZE[1]); const ctx = canvas.getContext("2d"); if (needsInit) { // First frame: fill white, then apply base state ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, canvas.width, canvas.height); if ( fs.existsSync("./frames/" + getFilenameForFrameID(frameId - 1) + ".png") ) { // if a previous frame exists, use the content to prevent large queries const prevFilename = "./frames/" + frameFilename(index - 1) + ".png"; if (fs.existsSync(prevFilename)) { // Load previous worker's last frame as the base const prevFrame = await Canvas.loadImage( fs.createReadStream( "./frames/" + getFilenameForFrameID(frameId - 1) + ".png", ), fs.createReadStream(prevFilename), ); ctx.drawImage( prevFrame, Loading @@ -110,25 +99,30 @@ const generateFrame = async (frameId: number, pixels: Pixel[]) => { prevFrame.height, ); } else { // No previous frame — load all existing pixels from DB let startTime: Date; if (pixels.length > 0) { startTime = new Date(pixels[0].date); } else { startTime = workerStart; } // no previous frame exists, guess we will have to load from the database const previousPixels = await query.existingPixelsBeforeTime(startTime); pixels = [...dbPixelsToLog(previousPixels.rows), ...pixels]; const previousPixels = await query.existingPixelsBeforeTime(startTime); for (const p of dbPixelsToLog(previousPixels.rows)) { ctx.fillStyle = p.hex === "unset" ? "#FFFFFF" : "#" + p.hex; ctx.fillRect( p.x * pixelScale[0], p.y * pixelScale[1], pixelScale[0], pixelScale[1], ); } } const pixelScale = [ OUTPUT_SIZE[0] / CANVAS_SIZE[0], OUTPUT_SIZE[1] / CANVAS_SIZE[1], ]; needsInit = false; } let i = 0; // Draw this frame's pixel changes on top of the existing canvas for (const pixel of pixels) { ctx.fillStyle = pixel.hex === "unset" ? "#FFFFFF" : "#" + pixel.hex; ctx.fillRect( Loading @@ -137,26 +131,30 @@ const generateFrame = async (frameId: number, pixels: Pixel[]) => { pixelScale[0], pixelScale[1], ); i++; } let filename = getFilenameForFrameID(frameId); await canvas .encode("png") .then((data) => fs.promises.writeFile(`./frames/${filename}.png`, data as Uint8Array), ); log( "wrote frame " + frameId + " in " + ((Date.now() - start) / 1000).toFixed(2) + "s", // Encode current canvas state to PNG and write const data = await canvas.encode("png"); await fs.promises.writeFile( `./frames/${frameFilename(index)}.png`, data as Uint8Array, ); }; const getFilenameForFrameID = (frameId: number) => parentPort?.postMessage({ type: "progress", progress: (index - msg.frameStart) / framesToGenerate, }); index++; } log(`Finished in ${((Date.now() - start) / 1000).toFixed(2)} seconds`); parentPort?.postMessage({ type: "finished" }); }); } } }); const frameFilename = (frameId: number) => "0".repeat(Math.max(0, 5 - (frameId + "").length)) + frameId; const getPixelsAsLog = async (start: number, end: number) => { Loading Loading @@ -249,7 +247,8 @@ const getPixelsAsLog = async (start: number, end: number) => { }); } else if (dbpixel.deletedAt) { // this is a deleted pixel that was in the timeframe const cacheKey = dbpixel.x + "," + dbpixel.y + ":" + dbpixel.createdAt.getTime(); const cacheKey = dbpixel.x + "," + dbpixel.y + ":" + dbpixel.createdAt.getTime(); pixels.push({ x: dbpixel.x, y: dbpixel.y, Loading Loading
src/render_worker.ts +80 −81 Original line number Diff line number Diff line Loading @@ -58,45 +58,34 @@ parentPort?.on("message", (msg: Message) => { const start = Date.now(); const framesToGenerate = (msg.end - msg.start) / msg.step; // Create one canvas that persists across all frames in this worker. // Subsequent frames draw on the same canvas — no PNG decode needed. const canvas = Canvas.createCanvas(OUTPUT_SIZE[0], OUTPUT_SIZE[1]); const ctx = canvas.getContext("2d"); const pixelScale = [ OUTPUT_SIZE[0] / CANVAS_SIZE[0], OUTPUT_SIZE[1] / CANVAS_SIZE[1], ]; let index = msg.frameStart; let needsInit = true; for (let time = msg.start; time < msg.end; time += msg.step) { const pixels = await getPixelsAsLog(time, time + msg.step); log("generating frame " + index); await generateFrame(index, pixels); parentPort?.postMessage({ type: "progress", progress: (index - msg.frameStart) / framesToGenerate, }); index++; } log(`Finished in ${((Date.now() - start) / 1000).toFixed(2)} seconds`); parentPort?.postMessage({ type: "finished" }); }); break; } } }); const generateFrame = async (frameId: number, pixels: Pixel[]) => { const start = Date.now(); const canvas = Canvas.createCanvas(OUTPUT_SIZE[0], OUTPUT_SIZE[1]); const ctx = canvas.getContext("2d"); if (needsInit) { // First frame: fill white, then apply base state ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, canvas.width, canvas.height); if ( fs.existsSync("./frames/" + getFilenameForFrameID(frameId - 1) + ".png") ) { // if a previous frame exists, use the content to prevent large queries const prevFilename = "./frames/" + frameFilename(index - 1) + ".png"; if (fs.existsSync(prevFilename)) { // Load previous worker's last frame as the base const prevFrame = await Canvas.loadImage( fs.createReadStream( "./frames/" + getFilenameForFrameID(frameId - 1) + ".png", ), fs.createReadStream(prevFilename), ); ctx.drawImage( prevFrame, Loading @@ -110,25 +99,30 @@ const generateFrame = async (frameId: number, pixels: Pixel[]) => { prevFrame.height, ); } else { // No previous frame — load all existing pixels from DB let startTime: Date; if (pixels.length > 0) { startTime = new Date(pixels[0].date); } else { startTime = workerStart; } // no previous frame exists, guess we will have to load from the database const previousPixels = await query.existingPixelsBeforeTime(startTime); pixels = [...dbPixelsToLog(previousPixels.rows), ...pixels]; const previousPixels = await query.existingPixelsBeforeTime(startTime); for (const p of dbPixelsToLog(previousPixels.rows)) { ctx.fillStyle = p.hex === "unset" ? "#FFFFFF" : "#" + p.hex; ctx.fillRect( p.x * pixelScale[0], p.y * pixelScale[1], pixelScale[0], pixelScale[1], ); } } const pixelScale = [ OUTPUT_SIZE[0] / CANVAS_SIZE[0], OUTPUT_SIZE[1] / CANVAS_SIZE[1], ]; needsInit = false; } let i = 0; // Draw this frame's pixel changes on top of the existing canvas for (const pixel of pixels) { ctx.fillStyle = pixel.hex === "unset" ? "#FFFFFF" : "#" + pixel.hex; ctx.fillRect( Loading @@ -137,26 +131,30 @@ const generateFrame = async (frameId: number, pixels: Pixel[]) => { pixelScale[0], pixelScale[1], ); i++; } let filename = getFilenameForFrameID(frameId); await canvas .encode("png") .then((data) => fs.promises.writeFile(`./frames/${filename}.png`, data as Uint8Array), ); log( "wrote frame " + frameId + " in " + ((Date.now() - start) / 1000).toFixed(2) + "s", // Encode current canvas state to PNG and write const data = await canvas.encode("png"); await fs.promises.writeFile( `./frames/${frameFilename(index)}.png`, data as Uint8Array, ); }; const getFilenameForFrameID = (frameId: number) => parentPort?.postMessage({ type: "progress", progress: (index - msg.frameStart) / framesToGenerate, }); index++; } log(`Finished in ${((Date.now() - start) / 1000).toFixed(2)} seconds`); parentPort?.postMessage({ type: "finished" }); }); } } }); const frameFilename = (frameId: number) => "0".repeat(Math.max(0, 5 - (frameId + "").length)) + frameId; const getPixelsAsLog = async (start: number, end: number) => { Loading Loading @@ -249,7 +247,8 @@ const getPixelsAsLog = async (start: number, end: number) => { }); } else if (dbpixel.deletedAt) { // this is a deleted pixel that was in the timeframe const cacheKey = dbpixel.x + "," + dbpixel.y + ":" + dbpixel.createdAt.getTime(); const cacheKey = dbpixel.x + "," + dbpixel.y + ":" + dbpixel.createdAt.getTime(); pixels.push({ x: dbpixel.x, y: dbpixel.y, Loading