91 lines
2.5 KiB
TypeScript
91 lines
2.5 KiB
TypeScript
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<void> {
|
|
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<void>((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}`));
|
|
});
|
|
}),
|
|
),
|
|
);
|
|
}
|