From ea9a10b57899ab0d811f7603da6dba90060bf4e2 Mon Sep 17 00:00:00 2001 From: warlock Date: Sat, 11 Oct 2025 22:57:53 +0530 Subject: [PATCH] progressive log --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 8 +++-- src/index.ts | 75 ++++++++++++++++++++++++++++++++++++-------- src/utils/drawer.ts | 11 ++++--- 4 files changed, 149 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d1a91f..808dda9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,25 @@ "name": "canvas-hello-world", "version": "1.0.0", "dependencies": { - "canvas": "^3.2.0" + "canvas": "^3.2.0", + "cli-progress": "^3.12.0" }, "devDependencies": { + "@types/cli-progress": "^3.11.6", "@types/node": "^20.0.0", "typescript": "^5.0.0" } }, + "node_modules/@types/cli-progress": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", + "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.19.tgz", @@ -25,6 +37,15 @@ "undici-types": "~6.21.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -100,6 +121,18 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -133,6 +166,12 @@ "node": ">=8" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -195,6 +234,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -406,6 +454,32 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", diff --git a/package.json b/package.json index c7e2ed1..2ce92d1 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "watch": "tsc --watch" }, "devDependencies": { - "typescript": "^5.0.0", - "@types/node": "^20.0.0" + "@types/cli-progress": "^3.11.6", + "@types/node": "^20.0.0", + "typescript": "^5.0.0" }, "dependencies": { - "canvas": "^3.2.0" + "canvas": "^3.2.0", + "cli-progress": "^3.12.0" } } diff --git a/src/index.ts b/src/index.ts index 0bfaadb..c4d8a4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import { createCanvas } from "canvas"; +import { createCanvas, loadImage } from "canvas"; import { readReplay } from "./utils/replay_reader"; import { drawTimeBasedHeatmap, getReplayDuration } from "./utils/drawer"; - +import * as cliProgress from "cli-progress"; const replayPath = "/home/warlock/.var/app/com.unity.UnityHub/config/unity3d/Milk Carton Games/Super Cloudfight/Replays/"; const fs = require("fs"); @@ -12,7 +12,14 @@ const replayFiles = fs.readdirSync(replayPath) .filter((file: string) => file.endsWith(".json")) .map((file: string) => path.join(replayPath, file)); -replayFiles.forEach(async (filePath: string) => { +// Create multibar container +const multibar = new cliProgress.MultiBar({ + clearOnComplete: false, + hideCursor: true, + format: '{bar} | {filename} | {view} | {value}/{total} frames | {percentage}%' +}, cliProgress.Presets.shades_classic); + +async function processReplayFile(filePath: string) { const replayData = readReplay(filePath); const baseFileName = path.basename(filePath, '.json'); @@ -23,7 +30,6 @@ replayFiles.forEach(async (filePath: string) => { // Get replay duration in seconds const duration = getReplayDuration(replayData); - console.log(`Processing ${baseFileName}: ${duration} seconds (${framesPerSecond} fps @ ${timelapseSpeed}x speed)`); // Create main directory for this replay const replayDir = baseFileName; @@ -38,26 +44,45 @@ replayFiles.forEach(async (filePath: string) => { { mode: 'front', dirName: 'front' } ]; + // Calculate total number of frames to generate + const totalFrames = Math.ceil(duration * framesPerSecond); + + // Create progress bars for each view + const progressBars = new Map(); + for (const { mode } of views) { + const bar = multibar.create(totalFrames, 0, { + filename: baseFileName, + view: mode.padEnd(5, ' ') + }); + progressBars.set(mode, bar); + } + + // Reuse a single canvas for all frames to reduce memory allocation + const canvas = createCanvas(1024, 1024); + for (const { mode, dirName } of views) { const viewDir = path.join(replayDir, dirName); if (!fs.existsSync(viewDir)) { fs.mkdirSync(viewDir); } - // Get background image path + // Get background image path and preload it once const backgroundImagePath = path.join(__dirname, '..', 'maps', 'hurricane', `${mode}.png`); + let preloadedBackground; + try { + preloadedBackground = await loadImage(backgroundImagePath); + } catch (error) { + console.error(`Failed to preload background image: ${backgroundImagePath}`, error); + } - // Calculate total number of frames to generate - const totalFrames = Math.ceil(duration * framesPerSecond); + const progressBar = progressBars.get(mode)!; // Generate heatmaps at the calculated frame rate for (let frameNumber = 0; frameNumber < totalFrames; frameNumber++) { // Calculate the time point for this frame const timePoint = frameNumber / framesPerSecond; - const canvas = createCanvas(1024, 1024); - - // Generate time-based heatmap + // Generate time-based heatmap (reusing the same canvas) await drawTimeBasedHeatmap(canvas, replayData, timePoint, { width: 200, // Heatmap resolution height: 200, @@ -65,12 +90,36 @@ replayFiles.forEach(async (filePath: string) => { sensitivity: 0.7, // Controls sensitivity to lower values (0.1-2.0) blurAmount: 3, // Controls smoothness of heat areas (1-5) viewMode: mode, // View mode: 'top', 'side', or 'front' - backgroundImage: backgroundImagePath + preloadedBackground: preloadedBackground }); const outputPath = path.join(viewDir, `${(frameNumber + 1).toString().padStart(3, '0')}.png`); fs.writeFileSync(outputPath, canvas.toBuffer("image/png")); - console.log(`${outputPath} (${mode} view, frame ${frameNumber + 1}/${totalFrames}, t=${timePoint.toFixed(2)}s)`); + + // Update progress bar + progressBar.update(frameNumber + 1); + + // Allow the event loop to process (enables real-time progress bar updates) + if (frameNumber % 5 === 0) { + await new Promise(resolve => setImmediate(resolve)); + } + } + + // Force garbage collection hint after each view + if (global.gc) { + global.gc(); } } -}); +} + +// Process all replay files sequentially +(async () => { + for (const filePath of replayFiles) { + await processReplayFile(filePath); + } + + // Stop multibar after all processing is complete + multibar.stop(); +})(); + + diff --git a/src/utils/drawer.ts b/src/utils/drawer.ts index f407dd2..7c1d429 100644 --- a/src/utils/drawer.ts +++ b/src/utils/drawer.ts @@ -1,4 +1,4 @@ -import { Canvas, loadImage } from "canvas"; +import { Canvas, loadImage, Image } from "canvas"; import { ReplayData } from "../types/replay"; import * as path from "path"; @@ -12,6 +12,7 @@ interface HeatmapConfig { blurAmount: number; viewMode: ViewMode; backgroundImage?: string; + preloadedBackground?: Image; } const defaultConfig: HeatmapConfig = { @@ -222,7 +223,7 @@ export const drawTimeBasedHeatmap = async (canvas: Canvas, replayData: ReplayDat } } - console.log(`Time ${maxTime}s - Good: ${good}, Bad: ${bad}`); + // console.log(`Time ${maxTime}s - Good: ${good}, Bad: ${bad}`); // Find maximum count for normalization let maxCount = 0; @@ -264,8 +265,10 @@ export const drawTimeBasedHeatmap = async (canvas: Canvas, replayData: ReplayDat ctx.fillStyle = "white"; ctx.fillRect(0, 0, canvas.width, canvas.height); - // Draw background image if provided - if (finalConfig.backgroundImage) { + // Draw background image if provided (use preloaded image if available) + if (finalConfig.preloadedBackground) { + ctx.drawImage(finalConfig.preloadedBackground, 0, 0, canvas.width, canvas.height); + } else if (finalConfig.backgroundImage) { try { const bgImage = await loadImage(finalConfig.backgroundImage); ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height);