progressive log
This commit is contained in:
parent
c8feceedca
commit
ea9a10b578
76
package-lock.json
generated
76
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
src/index.ts
75
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<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();
|
||||
})();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user