import { createRequire } from "node:module"; import os from "node:os"; import path from "node:path"; import type { DrawStyle } from "@/lib/card-gen/id-config"; export type GenerateTask = { text: string; outPath: string; }; export type WorkerFont = { path: string; family: string; }; type WorkerMsg = | { type: "progress" } | { type: "error"; message: string }; export async function generatePngsInWorkers(args: { imagePath: string; fonts: WorkerFont[]; style: DrawStyle; tasks: GenerateTask[]; onProgress: (current: number, total: number) => void; }): Promise { const { imagePath, fonts, style, tasks, onProgress } = args; if (tasks.length === 0) return; const require = createRequire(path.join(process.cwd(), "package.json")); const threads = require("node:worker_threads") as typeof import("node:worker_threads"); const workerCount = Math.max( 1, Math.min(os.cpus().length || 1, 8, tasks.length), ); const buckets: GenerateTask[][] = Array.from( { length: workerCount }, () => [], ); tasks.forEach((task, i) => { buckets[i % workerCount]!.push(task); }); const workerPath = path.join(process.cwd(), "workers", "generate-id.cjs"); let completed = 0; const total = tasks.length; await Promise.all( buckets .filter((bucket) => bucket.length > 0) .map( (bucket) => new Promise((resolve, reject) => { const worker = new threads.Worker(workerPath, { workerData: { imagePath, fonts, style, tasks: bucket, }, }); let settled = false; const fail = (err: Error) => { if (settled) return; settled = true; void worker.terminate(); reject(err); }; worker.on("message", (msg: WorkerMsg) => { if (msg?.type === "progress") { completed += 1; onProgress(completed, total); return; } if (msg?.type === "error") { fail(new Error(msg.message || "Worker error")); } }); worker.on("error", (err) => fail(err)); worker.on("exit", (code) => { if (settled) return; settled = true; if (code === 0) resolve(); else reject(new Error(`Worker exited with code ${code}`)); }); }), ), ); }