import type { WorkerFunction , WorkerInitFunction , WorkerFunctionTransferArgs } from './worker-wrapper' ; import Scheduler from './scheduler' ; import WebWorker from './worker-wrapper' ; import calculateMedian from './median' ; export type { WorkerFunction , WorkerInitFunction , WorkerFunctionTransferArgs } ; /**
    ### Welcome to Rinzler's full documentation.
    If you're just getting started, check out the [Quick Start guide](../index.html#quick-start).

    This page describes the interface of an instanted RinzlerEngine class, which you can access like so:

    ```js
    import RinzlerEngine from 'rinzler-engine'

    const engine = new RinzlerEngine()
    ```
*/ export default class RinzlerEngine { #maxTemp = ( navigator . hardwareConcurrency && navigator . hardwareConcurrency > 1 ) ? navigator . hardwareConcurrency - 1 : 1 ; #targetTemp = - 1 ; #minTemp = 1 ; #workerArgs ? : ConstructorParameters < typeof WebWorker > ; #scheduler : Scheduler = new Scheduler ( ) ; #coolingTimer : [ number , string ] | null = null ; #extendPoolTimes : number [ ] = [ ] ; readonly #coolingDelay = 4000 ; readonly #extendPoolTimesBacklogSize = 20 ; param workFunction param initFunction param initArgs returns category async configureAndStart ( workFunction , initFunction ? , initArgs ? ) { if ( this . #scheduler . workerPool . size ) throw new Error ( 'Rinzler: engine is already started, please use shutdown() first' ) ; this . #workerArgs = [ workFunction , initFunction , initArgs ] ; this . #targetTemp = 0 ; await this . #heatUp ( ) ; return this ; } typeParam returns category async runJob < T = void > ( ... args ) { await this . #automaticHeatUp ( ) ; return this . #scheduler . submitJob < T > ( args [ 0 ] , args [ 1 ] ) ; } category async shutdown ( ) { await this . #coolDown ( this . #scheduler . workerPool . size + 1 , true ) ; } /**
        Start as much WebWorkers as currently recommended by the browser, or allowed by  afterburner .

        You should use this only if you know you will have to process many jobs and want to ensure that the engine is fully revved up.
        In most cases the automatic "temperature" adjustement will provide a good balance between performance and memory footprint.

        You should immediatly follow this call with your actual  runJob calls, otherwise the engine might start to cool itself down automatically.

         returns category async preHeat ( ) { await this . #heatUp ( this . #maxTemp - this . #targetTemp ) ; return this ; } param max returns category async afterburner ( max ) { const current = this . #measureTemp ( ) ; this . #maxTemp = max ; if ( ( current - max ) > 0 ) { await this . #coolDown ( current - max ) ; } return this ; } _debug ( ) { return { maxTemp : this . #maxTemp , targetTemp : this . #targetTemp , temp : this . #measureTemp ( ) , minTemp : this . #minTemp , scheduler : this . #scheduler , coolingTimer : this . #coolingTimer , coolingDelay : this . #coolingDelay , medianExtendPoolTime : this . #measureMedianForkTime ( ) } ; } #measureTemp ( ) { return this . #scheduler . workerPool . size ; } #measureMedianForkTime ( ) { return calculateMedian ( this . #extendPoolTimes ) ; } async #automaticHeatUp ( ) { const { jobCount } = this . #scheduler . getLeastBusyWorker ( ) ; if ( jobCount * this . #scheduler . measureMedianExecTime ( ) > this . #measureMedianForkTime ( ) ) { try { await this . #heatUp ( ) ; } catch { } } } async #heatUp ( threadCount = 1 ) { if ( ! this . #workerArgs ) throw new Error ( 'Rinzler (internal): unknown parameters for heating up new threads' ) ; if ( ( this . #targetTemp + threadCount ) > this . #maxTemp ) throw new Error ( 'Rinzler (internal): attempting to overheat' ) ; if ( this . #targetTemp < 0 ) throw new Error ( 'Rinzler (internal): engine is too cold, likely has already been shut down' ) ; this . #targetTemp = ( this . #measureTemp ( ) + threadCount ) || threadCount ; for ( let i = 0 ; i < threadCount ; i ++ ) { const perfMarkA = performance . now ( ) ; const worker = new WebWorker ( ... this . #workerArgs ) ; const wid = await this . #scheduler . extendPool ( worker ) ; worker . on ( 'idle' , ( ) => { this . #automaticCoolDown ( wid ) ; } ) ; await worker . start ( ) ; const perfMarkB = performance . now ( ) ; this . #extendPoolTimes . unshift ( perfMarkB - perfMarkA ) ; this . #extendPoolTimes . splice ( this . #extendPoolTimesBacklogSize , this . #extendPoolTimes . length - this . #extendPoolTimesBacklogSize ) ; } } #automaticCoolDown ( wid ? ) { if ( ! wid ) { const { wid , jobCount } = this . #scheduler . getLeastBusyWorker ( ) ; if ( jobCount === 0 ) { this . #automaticCoolDown ( wid ) ; } return ; } if ( this . #coolingTimer ) return ; if ( this . #measureTemp ( ) <= this . #minTemp ) { this . #coolingTimer = null ; return ; } const worker = this . #scheduler . workerPool . get ( wid ) ; if ( ! worker ) return ; this . #coolingTimer = [ window . setTimeout ( async ( ) => { try { await this . #coolDown ( 1 , false , wid ) ; } catch { } this . #coolingTimer = null ; this . #automaticCoolDown ( ) ; } , this . #coolingDelay ) , wid ] ; worker . once ( 'jobok' , ( ) => { if ( this . #coolingTimer && this . #coolingTimer [ 1 ] === wid ) { window . clearTimeout ( this . #coolingTimer [ 0 ] ) ; this . #coolingTimer = null ; this . #automaticCoolDown ( ) ; } } ) ; } async #coolDown ( threadCount = 1 , override = false , wid ? ) { if ( ! override ) { if ( ( this . #targetTemp - threadCount ) < this . #minTemp ) throw new Error ( 'Rinzler (internal): attempting to underheat' ) ; if ( ( this . #targetTemp - threadCount ) <= 0 ) throw new Error ( 'Rinzler (internal): engine cannot run in subzero temperatures' ) ; } if ( wid && threadCount !== 1 ) throw new Error ( 'Rinzler (internal): cannot cool down more than 1 workers when specifying a worked id' ) ; this . #targetTemp = this . #measureTemp ( ) - threadCount ; for ( let i = 0 ; i < threadCount ; i ++ ) { ( wid ) ? await this . #scheduler . reducePool ( wid ) : await this . #scheduler . reducePool ( ) ; } } }