import { TypedArray } from './Heap' type WorkerId = number type UserData = any type ResolveCallback = (value: { buffer: TypedArray; userData: UserData }) => void type RejectCallback = (reason: { userData: UserData; error: ErrorEvent }) => void export class WorkerPool { // Array of web workers private workers = [] // Array of corresponding worker queues private infos: { queue: { resolve: ResolveCallback; reject: RejectCallback; userData: any }[] }[] = [] private aborted = false constructor(numWorkers: number, workerScriptURL: string) { if (!window['Worker']) { console.error('WebWorker support required') return } for (let i = 0; i < numWorkers; i++) { const worker = new Worker(workerScriptURL) worker.onmessage = msg => { const { userData, resolve } = this.infos[i].queue.shift() if (this.aborted) { return } if (!msg.data['finalResponse']) { console.error('emscripten_worker_respond_provisionally is not supported') return } resolve({ buffer: msg.data.data, userData }) } worker.onerror = msg => { console.error( `Worker call failed. Is worker's script URL "${workerScriptURL}" correct? Message:`, msg.message, ) // The queue is empty if "onerror" was called for worker load failure, so we need this check. if (this.infos[i].queue.length > 0) { const { userData, reject } = this.infos[i].queue.shift() reject({ userData, error: msg }) } } const info = { queue: [], } this.workers.push(worker) this.infos.push(info) } } destroy() { this.aborted = true for (const w of this.workers) { w.terminate() } } /** * Sends a binary message to a WebWorker. */ callWorker( id: WorkerId, funcName: string, message: TypedArray, userData: UserData, ): Promise<{ buffer: TypedArray; userData: any }> { const transferObject = { funcName: funcName, callbackId: '', // not needed since we push callbacks to a queue in order data: message, } // We transfer the ownership of "message" to the worker. This means it can't point // to a view of the Emscripten heap because the whole heap would get transferred! this.workers[id].postMessage(transferObject, [message.buffer]) // The promise will settle when a worker response triggers "onmessage" callback // above and calls one the "resolve" and "reject" functions off the queue. // The worker is guaranteed to handle and respond to messages in order, so this // all works out nicely. return new Promise((resolve, reject) => { this.infos[id].queue.push({ resolve, reject, userData }) }) } findMostAvailableWorker() { let best_id: WorkerId = -1 let best = 1e9 for (let i = 0; i < this.workers.length; i++) { if (this.infos[i].queue.length < best) { best_id = i best = this.infos[i].queue.length } } return best_id } shortestQueueLength() { let shortest = 1e9 for (const info of this.infos) { shortest = Math.min(shortest, info.queue.length) } return shortest } numLoadsPending() { let sum = 0 for (const info of this.infos) { sum += info.queue.length } return sum } numWorkers() { return this.workers.length } }