progressive log

This commit is contained in:
warlock 2025-10-11 22:57:53 +05:30
parent c8feceedca
commit ea9a10b578
4 changed files with 149 additions and 21 deletions

76
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -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<string, cliProgress.SingleBar>();
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();
})();

View File

@ -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);