UNPKG

deadem

Version:

JavaScript (Node.js & Browsers) parser for Deadlock (Valve Source 2 Engine) demo/replay files

202 lines (157 loc) 5.23 kB
import Assert from '#core/Assert.js'; import Logger from '#core/Logger.js'; import DeferredPromise from '#data/DeferredPromise.js'; import WorkerThread from '#workers/WorkerThread.js'; class WorkerManager { /** * @constructor * @param {number} concurrency - Number of worker threads to manage. * @param {Logger} logger - Logger. */ constructor(concurrency, logger) { Assert.isTrue(concurrency > 0 && Number.isInteger(concurrency), `Invalid concurrency argument [ ${concurrency} ]`); Assert.isTrue(logger instanceof Logger); this._concurrency = concurrency; this._logger = logger; this._allocations = new Set(); this._queue = [ ]; this._threads = [ ]; } /** * The number of threads managed by this instance. * * @returns {number} */ get concurrency() { return this._concurrency; } /** * Allocates a free worker thread if available, or waits for one to be freed. * * @public * @param {number=} id - A thread id. * @returns {Promise<WorkerThread>} - A promise that resolves to an available worker thread. */ async allocate(id) { Assert.isTrue(id === undefined || (Number.isInteger(id) && id < this._concurrency)); let thread; if (Number.isInteger(id)) { thread = !this._allocations.has(id) ? this._threads[id] : null; } else { thread = this._threads.find(t => !this._allocations.has(t.localId)) || null; } if (thread !== null) { this._allocations.add(thread.localId); return thread; } else { const item = createQueueItem(id); this._queue.push(item); return item.deferred.promise; } } /** * Allocates all available threads. * * @public * @returns {Array<Promise<WorkerThread>>} - An array of promises, each resolving to an allocated WorkerThread. */ allocateAll() { const promises = [ ]; for (let i = 0; i < this._concurrency; i++) { const promise = this.allocate(i); promises.push(promise); } return promises; } /** * Broadcasts a {@link WorkerRequest} to all threads. * * @param {WorkerRequest} request - The request. * @returns {Promise<Array<*>>} - An array of length === concurrency, where each element is a result promise. */ async broadcast(request) { const allocations = this.allocateAll(); const requests = allocations.map((promise) => { return promise .then(async (thread) => { const response = await thread.send(request); this.free(thread); return response; }); }); return Promise.all(requests); } /** * @public * @returns {boolean} */ getIsAllBusy() { return this._threads.every(t => t.busy); } /** * Frees a previously allocated thread and resolves the next waiting promise if any. * * @public * @param {WorkerThread} thread - The thread to free. */ free(thread) { Assert.isTrue(thread instanceof WorkerThread); if (thread.busy) { throw new Error(`Unable to free a busy thread [ ${thread.localId} ]`); } if (this._queue.length === 0) { this._allocations.delete(thread.localId); } const targetIndex = this._queue.findIndex(item => item.id === thread.localId); if (targetIndex >= 0) { const [ target ] = this._queue.splice(targetIndex, 1); target.deferred.resolve(thread); return; } const availableIndex = this._queue.findIndex(item => item.id === null); if (availableIndex >= 0) { const [ target ] = this._queue.splice(availableIndex, 1); target.deferred.resolve(thread); } else { this._allocations.delete(thread.localId); } } /** * Terminates all threads and clears internal state. * * @public */ async terminate() { const wait = async () => { if (this._allocations.size > 0) { await Promise.all( this._threads .filter(t => t.deferred !== null) .map(t => t.deferred.promise) ); await wait(); } }; await wait(); const busy = this._threads.filter(t => t.busy).length; if (busy > 0) { throw new Error(`Unable to terminate WorkerManager - there are [ ${busy} ] threads`); } this._threads.forEach((thread) => { thread.worker.terminate(); this._logger.info(`Terminated Worker [ ${thread.localId} ]`); }); this._threads = [ ]; } } function createQueueItem(id) { return { deferred: new DeferredPromise(), id: Number.isInteger(id) ? id : null }; } export default WorkerManager;