diff --git a/scripts/build.ts b/scripts/build.ts index 5be7d5c57..f5deea4fb 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,6 +1,14 @@ import { build } from 'esbuild'; +import { readdir } from 'fs/promises'; -const FILES = ['bot', 'index']; +const FILES = [ + 'bot', + 'index', + + (await readdir('src/workers')).map( + (file) => `workers/${file.replace('.ts', '')}`, + ), +]; await build({ entryPoints: FILES.map((file) => `src/${file}.ts`), diff --git a/src/core/blitzkrieg/jsxToPngThreaded.ts b/src/core/blitzkrieg/jsxToPngThreaded.ts new file mode 100644 index 000000000..5a68fb629 --- /dev/null +++ b/src/core/blitzkrieg/jsxToPngThreaded.ts @@ -0,0 +1,9 @@ +import jsxToSvg from './jsxToSvg'; +import { svgToPngThreaded } from './svgToPngThreaded'; + +export default async function jsxToPngThreaded(jsx: JSX.Element) { + const svg = await jsxToSvg(jsx); + const png = await svgToPngThreaded(svg); + + return png; +} diff --git a/src/core/blitzkrieg/svgToPngThreaded.ts b/src/core/blitzkrieg/svgToPngThreaded.ts new file mode 100644 index 000000000..dbba997b4 --- /dev/null +++ b/src/core/blitzkrieg/svgToPngThreaded.ts @@ -0,0 +1,42 @@ +import { Worker } from 'worker_threads'; + +const MAX_WORKERS = 3; + +const workers = [...Array(MAX_WORKERS)].map(() => { + const worker = new Worker(`${__dirname}/workers/render.cjs`); + console.log(`🟡 Launching render worker ${worker.threadId}`); + return worker; +}); +const queue: { + svg: string; + resolve: (png: Buffer) => void; +}[] = []; + +function manageQueue() { + if (queue.length > 0 && workers.length > 0) { + const { svg, resolve } = queue.shift()!; + const worker = workers.shift()!; + + function cleanup() { + workers.push(worker); + manageQueue(); + } + + worker.postMessage(svg); + worker.on('message', (png: Uint8Array) => { + cleanup(); + resolve(Buffer.from(png)); + }); + worker.on('error', (error) => { + cleanup(); + throw error; + }); + } +} + +export function svgToPngThreaded(svg: string) { + return new Promise((resolve) => { + queue.push({ svg, resolve }); + manageQueue(); + }); +} diff --git a/src/core/discord/normalizeInteractionReturnable.ts b/src/core/discord/normalizeInteractionReturnable.ts index 5f8da39da..cdb0e2208 100644 --- a/src/core/discord/normalizeInteractionReturnable.ts +++ b/src/core/discord/normalizeInteractionReturnable.ts @@ -6,7 +6,7 @@ import { InteractionEditReplyOptions, } from 'discord.js'; import { InteractionReturnable } from '../../events/interactionCreate'; -import jsxToPng from '../blitzkrieg/jsxToPng'; +import jsxToPngThreaded from '../blitzkrieg/jsxToPngThreaded'; export default async function normalizeInteractionReturnable( returnable: InteractionReturnable, @@ -37,7 +37,7 @@ export default async function normalizeInteractionReturnable( } else if (item === null) { return; } else { - const image = await jsxToPng(item); + const image = await jsxToPngThreaded(item); if (!reply.files) reply.files = []; reply.files.push(image); } diff --git a/src/events/ready.ts b/src/events/ready.ts index 50ecc620d..3c24d0ab2 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,5 +1,5 @@ import { Client } from 'discord.js'; export default function ready(client: Client) { - console.log(`🟢 Bot ${client.shard?.ids[0]} launched`); + console.log(`🟢 Launched bot ${client.shard?.ids[0]}`); } diff --git a/src/index.ts b/src/index.ts index b68ca4c46..75effcb7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import { manager } from './core/discord/manager'; manager.on('shardCreate', (shard) => { console.log(`🟡 Launching shard ${shard.id}`); - shard.on('ready', () => console.log(`🟢 Shard ${shard.id} launched`)); + shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`)); }); manager.spawn(); diff --git a/src/workers/render.ts b/src/workers/render.ts new file mode 100644 index 000000000..d6ad642c9 --- /dev/null +++ b/src/workers/render.ts @@ -0,0 +1,9 @@ +import { parentPort, threadId } from 'worker_threads'; +import svgToPng from '../core/blitzkrieg/svgToPng'; + +console.log(`🟢 Launched render worker ${threadId}`); + +parentPort?.on('message', async (svg: string) => { + const png = svgToPng(svg); + parentPort?.postMessage(png); +});