/** * WorkerPool.js * Hardware-scaling worker thread pool manager with zero-copy transfers and synchronous fallbacks. */ import { executeResampleZFallback, executeDeltaAndSimilarityFallback, executeVarianceSortFallback } from '../visualizer/ReliefMath.js'; export class WorkerPoolManager { constructor(size = null) { this.poolSize = size || Math.max(1, Math.min((typeof navigator !== 'undefined' && navigator.hardwareConcurrency) || 4, 8)); this.workers = []; this.idleWorkers = []; this.taskQueue = []; this.activeTransactions = new Map(); this.transactionId = 0; this.isSupported = typeof window !== 'undefined' && typeof window.Worker !== 'undefined'; if (this.isSupported) { this.initializePool(); } } initializePool() { for (let i = 0; i < this.poolSize; i++) { try { const worker = new Worker( new URL('../workers/mathWorker.js', import.meta.url), { type: 'module' } ); worker.onmessage = (e) => this.handleMessage(i, e.data); worker.onerror = (err) => this.handleError(i, err); this.workers.push(worker); this.idleWorkers.push(i); } catch (e) { this.isSupported = false; break; } } } postTask(taskName, payload, transferables = []) { if (!this.isSupported) { return this.runFallback(taskName, payload); } return new Promise((resolve, reject) => { const currentTxId = this.transactionId++; const task = { transactionId: currentTxId, taskName, payload, transferables, resolve, reject }; this.activeTransactions.set(currentTxId, task); this.taskQueue.push(task); this.processQueue(); }); } processQueue() { if (this.taskQueue.length === 0 || this.idleWorkers.length === 0) return; const workerIdx = this.idleWorkers.pop(); const task = this.taskQueue.shift(); const worker = this.workers[workerIdx]; try { worker.postMessage({ task: task.taskName, payload: task.payload, transactionId: task.transactionId }, task.transferables); } catch (err) { this.handleError(workerIdx, err); } } handleMessage(workerIdx, messageData) { const { transactionId, status, data, similarity, sortedIndices, variances, error } = messageData; const task = this.activeTransactions.get(transactionId); if (!task) return; this.activeTransactions.delete(transactionId); this.idleWorkers.push(workerIdx); if (status === 'success') { task.resolve({ data, similarity, sortedIndices, variances }); } else { task.reject(new Error(error)); } this.processQueue(); } handleError(workerIdx, error) { console.warn('[WorkerPool] Error en worker. Cambiando a ejecución síncrona.', error); this.isSupported = false; if (this.workers[workerIdx]) { try { this.workers[workerIdx].terminate(); } catch (_) {} } for (const [txId, task] of this.activeTransactions.entries()) { this.runFallback(task.taskName, task.payload) .then(task.resolve) .catch(task.reject); this.activeTransactions.delete(txId); } } runFallback(taskName, payload) { return new Promise((resolve, reject) => { try { switch (taskName) { case 'resampleMatrixZ': { const { sourceData, sourceZ, sourceX, targetZ, targetX } = payload; const res = executeResampleZFallback(sourceData, sourceZ, sourceX, targetZ, targetX); resolve({ data: res }); break; } case 'computeDeltaAndSimilarity': { const { matrixA, matrixB, length } = payload; const res = executeDeltaAndSimilarityFallback(matrixA, matrixB, length); resolve({ data: res.delta, similarity: res.similarity }); break; } case 'sortDimensionsByVariance': { const { data, rows, cols } = payload; const res = executeVarianceSortFallback(data, rows, cols); resolve({ sortedIndices: res.indices, variances: res.variances }); break; } default: reject(new Error(`Fallback unsupported for task: ${taskName}`)); } } catch (err) { reject(err); } }); } terminateAll() { this.workers.forEach(w => { try { w.terminate(); } catch (_) {} }); this.workers = []; this.idleWorkers = []; this.activeTransactions.clear(); } } export const WorkerPool = new WorkerPoolManager();