UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

289 lines (288 loc) 9.98 kB
"use strict"; /** * WorkerPool - Abstraction for parallel task execution * * This module provides: * - IWorkerPool interface (from ICommandContext) * - ThreadWorkerPool: Node.js worker_threads implementation * - SingleThreadPool: Executes tasks via worker but one at a time * * Both pools use the same TaskWorker.js script for execution, * ensuring consistent behavior between single and multi-threaded modes. * * The factory function createWorkerPool() selects the appropriate * implementation based on thread count. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ThreadWorkerPool = exports.SingleThreadPool = void 0; exports.createWorkerPool = createWorkerPool; const worker_threads_1 = require("worker_threads"); const path = __importStar(require("path")); /** * Get the path to the TaskWorker.mjs script. * The TaskWorker.mjs is in the same directory as the main CLI bundle. */ function getWorkerPath() { return path.resolve(__dirname, "TaskWorker.mjs"); } /** * SingleThreadPool executes tasks one at a time using a single worker. * This provides consistent behavior with ThreadWorkerPool but without parallelism. */ class SingleThreadPool { concurrency = 1; workerPath; memoryLimitMb; constructor(workerPath, memoryLimitMb = 16384) { this.workerPath = workerPath || getWorkerPath(); this.memoryLimitMb = memoryLimitMb; } async execute(task) { return executeInWorker(task, this.workerPath, this.memoryLimitMb); } async executeBatch(tasks, onProgress) { const results = []; for (let i = 0; i < tasks.length; i++) { const result = await this.execute(tasks[i]); results.push(result); if (onProgress) { onProgress(i + 1, tasks.length); } } return results; } async shutdown() { // Nothing to clean up - workers are created per-task } } exports.SingleThreadPool = SingleThreadPool; /** * ThreadWorkerPool uses Node.js worker_threads for parallel execution. */ class ThreadWorkerPool { concurrency; workerPath; memoryLimitMb; activeWorkers = new Set(); isShutdown = false; /** * Create a thread worker pool. * @param concurrency Maximum concurrent workers * @param workerPath Path to the worker script (TaskWorker.js) * @param memoryLimitMb Memory limit per worker in MB */ constructor(concurrency, workerPath, memoryLimitMb = 16384) { this.concurrency = Math.max(1, Math.min(concurrency, 8)); this.workerPath = workerPath || getWorkerPath(); this.memoryLimitMb = memoryLimitMb; } async execute(task) { if (this.isShutdown) { return { success: false, error: "Worker pool has been shutdown", }; } return new Promise((resolve) => { const worker = new worker_threads_1.Worker(this.workerPath, { resourceLimits: { maxOldGenerationSizeMb: this.memoryLimitMb, }, }); this.activeWorkers.add(worker); const cleanup = () => { worker.removeAllListeners(); this.activeWorkers.delete(worker); worker.terminate(); }; const onMessage = (result) => { cleanup(); // Handle error strings returned from worker if (typeof result === "string" && result.startsWith("Error")) { resolve({ success: false, error: result, }); } else { resolve({ success: true, result: result, }); } }; const onError = (error) => { cleanup(); resolve({ success: false, error: error.message, stack: error.stack, }); }; const onExit = (code) => { cleanup(); if (code !== 0) { resolve({ success: false, error: `Worker exited with code ${code}`, }); } }; worker.on("message", onMessage); worker.on("error", onError); worker.on("exit", onExit); // Post the task data to the worker worker.postMessage({ task: task.taskType, ...task.args, }); }); } async executeBatch(tasks, onProgress) { if (this.isShutdown) { return tasks.map(() => ({ success: false, error: "Worker pool has been shutdown", })); } const results = new Array(tasks.length); let currentIndex = 0; let completedCount = 0; const processNextBatch = async () => { const batchPromises = []; const batchIndices = []; // Start up to concurrency tasks for (let i = 0; i < this.concurrency && currentIndex < tasks.length; i++) { const taskIndex = currentIndex++; batchIndices.push(taskIndex); const promise = this.execute(tasks[taskIndex]).then((result) => { results[taskIndex] = result; completedCount++; if (onProgress) { onProgress(completedCount, tasks.length); } }); batchPromises.push(promise); } await Promise.all(batchPromises); }; // Process all tasks in batches while (currentIndex < tasks.length) { await processNextBatch(); } return results; } async shutdown() { this.isShutdown = true; const terminatePromises = []; for (const worker of this.activeWorkers) { terminatePromises.push(worker.terminate()); } await Promise.all(terminatePromises); this.activeWorkers.clear(); } } exports.ThreadWorkerPool = ThreadWorkerPool; /** * Execute a single task in a worker and return the result. * This is a helper used by both SingleThreadPool and ThreadWorkerPool. */ async function executeInWorker(task, workerPath, memoryLimitMb) { return new Promise((resolve) => { const worker = new worker_threads_1.Worker(workerPath, { resourceLimits: { maxOldGenerationSizeMb: memoryLimitMb, }, }); const cleanup = () => { worker.removeAllListeners(); worker.terminate(); }; const onMessage = (result) => { cleanup(); // Handle error strings returned from worker if (typeof result === "string" && result.startsWith("Error")) { resolve({ success: false, error: result, }); } else { resolve({ success: true, result: result, }); } }; const onError = (error) => { cleanup(); resolve({ success: false, error: error.message, stack: error.stack, }); }; const onExit = (code) => { cleanup(); if (code !== 0) { resolve({ success: false, error: `Worker exited with code ${code}`, }); } }; worker.on("message", onMessage); worker.on("error", onError); worker.on("exit", onExit); // Post the task data to the worker // Convert IWorkerTask to the format expected by TaskWorker worker.postMessage({ task: task.taskType, ...task.args, }); }); } /** * Factory function to create the appropriate worker pool. * @param threads Number of threads (1 = single-threaded, >1 = multi-threaded) * @param workerPath Optional path to worker script (defaults to TaskWorker.js) */ function createWorkerPool(threads, workerPath) { if (threads <= 1) { return new SingleThreadPool(workerPath); } return new ThreadWorkerPool(threads, workerPath); }