import assert from 'node:assert'; export abstract class AsynchronouslyCreatedResource { onreadyListeners : (() => void)[] | null = []; ondestroyListeners : (() => void)[] | null = []; markAsReady () : void { const listeners = this.onreadyListeners; assert(listeners !== null); this.onreadyListeners = null; for (const listener of listeners) { listener(); } } isReady () : boolean { return this.onreadyListeners === null; } onReady (fn : () => void) { if (this.onreadyListeners === null) { fn(); // Zalgo is okay here. return; } this.onreadyListeners.push(fn); } onDestroy (fn : () => void) { if (this.ondestroyListeners === null) { return; } this.ondestroyListeners.push(fn); } markAsDestroyed () { const listeners = this.ondestroyListeners; assert(listeners !== null); this.ondestroyListeners = null; for (const listener of listeners) { listener(); } } isDestroyed () { return this.ondestroyListeners === null; } abstract currentUsage() : number; } // TODO: this will eventually become an scheduler export class AsynchronouslyCreatedResourcePool< T extends AsynchronouslyCreatedResource> { pendingItems = new Set(); readyItems = new Set(); maximumUsage : number; onAvailableListeners : ((item : T) => void)[]; onTaskDoneListeners : ((item : T) => void)[]; constructor (maximumUsage : number) { this.maximumUsage = maximumUsage; this.onAvailableListeners = []; this.onTaskDoneListeners = []; } add (item : T) { this.pendingItems.add(item); item.onReady(() => { /* istanbul ignore else */ if (this.pendingItems.has(item)) { this.pendingItems.delete(item); this.readyItems.add(item); this.maybeAvailable(item); } }); } delete (item : T) { this.pendingItems.delete(item); this.readyItems.delete(item); } * [Symbol.iterator] () { yield * this.pendingItems; yield * this.readyItems; } get size () { return this.pendingItems.size + this.readyItems.size; } maybeAvailable (item : T) { /* istanbul ignore else */ if (item.currentUsage() < this.maximumUsage) { for (const listener of this.onAvailableListeners) { listener(item); } } } onAvailable (fn : (item : T) => void) { this.onAvailableListeners.push(fn); } taskDone (item : T) { for (let i = 0; i < this.onTaskDoneListeners.length; i++) { this.onTaskDoneListeners[i](item); } } onTaskDone (fn : (item : T) => void) { this.onTaskDoneListeners.push(fn); } getCurrentUsage (): number { let inFlight = 0; for (const worker of this.readyItems) { const currentUsage = worker.currentUsage(); if (Number.isFinite(currentUsage)) inFlight += currentUsage; } return inFlight; } }