import type { IWorkerMessage , INumberStatsMessageRequest , IAdvancedBoxPlotData , ICategoricalStatistics , IDateStatistics , IStatistics , ICategoricalStatsMessageRequest , IDateStatsMessageRequest , IBoxPlotStatsMessageRequest , } from './' ; import type { UIntTypedArray , IndicesArray } from '../model' ; import type { IStringStatsMessageRequest } from './math' ; import type { IStringStatistics } from './mathInterfaces' ; internal export interface IPoorManWorkerScopeEventMap { message : MessageEvent ; error : ErrorEvent ; } declare type IPoorManTransferAble = ArrayBuffer | MessagePort | ImageBitmap ; internal export interface IPoorManWorkerScope { onmessage : ( ( this : IPoorManWorkerScope , ev : MessageEvent ) => any ) | null ; onerror : ( ( this : IPoorManWorkerScope , ev : ErrorEvent ) => any ) | null ; close ( ) : void ; postMessage ( message , transfer ? ) : void ; addEventListener < K extends keyof IPoorManWorkerScopeEventMap > ( type , listener , options ? ) : void ; removeEventListener < K extends keyof IPoorManWorkerScopeEventMap > ( type , listener , options ? ) : void ; } internal export function toFunctionBody ( f ) { const source = f . toString ( ) ; return source . slice ( source . indexOf ( '{' ) + 1 , source . lastIndexOf ( '}' ) ) ; } internal export function createWorkerCodeBlob ( fs ) { const sources = fs . map ( ( d ) => d . toString ( ) ) . join ( '\n\n' ) ; const blob = new Blob ( [ sources ] , { type : 'application/javascript' } ) ; return URL . createObjectURL ( blob ) ; } const MIN_WORKER_THREADS = 1 ; const MAX_WORKER_THREADS = Math . max ( navigator . hardwareConcurrency - 1 , 1 ) ; const THREAD_CLEANUP_TIME = 10000 ; interface ITaskWorker { /**
     * worker index
     */ index : number ; /**
     * worker itself
     */ worker : Worker ; /**
     * set of active task numbers
     */ tasks : Set < number > ; /**
     * list of references that are stored on this worker
     */ refs : Set < string > ; } internal export class WorkerTaskScheduler { private readonly workers : ITaskWorker [ ] = [ ] ; private cleanUpWorkerTimer = - 1 ; /**
     * worker task id
     */ private workerTaskCounter = 0 ; constructor ( private readonly blob ) { for ( let i = 0 ; i < MIN_WORKER_THREADS ; ++ i ) { const w = new Worker ( blob ) ; this . workers . push ( { worker : w , tasks : new Set ( ) , refs : new Set ( ) , index : i } ) ; } } terminate ( ) { this . workers . splice ( 0 , this . workers . length ) . forEach ( ( w ) => w . worker . terminate ( ) ) ; } private readonly cleanUpWorker = ( ) => { while ( this . workers . length > MIN_WORKER_THREADS ) { const toFree = this . workers . findIndex ( ( d ) => d . tasks . size === 0 ) ; if ( toFree < 0 ) { break ; } const w = this . workers . splice ( toFree , 1 ) [ 0 ] ! ; w . worker . terminate ( ) ; } this . finishedTask ( ) ; } ; private checkOutWorker ( ) { if ( this . cleanUpWorkerTimer >= 0 ) { clearTimeout ( this . cleanUpWorkerTimer ) ; this . cleanUpWorkerTimer = - 1 ; } const emptyWorker = this . workers . find ( ( d ) => d . tasks . size === 0 ) ; if ( emptyWorker ) { return emptyWorker ; } if ( this . workers . length >= MAX_WORKER_THREADS ) { return this . workers . reduce ( ( a , b ) => ( a == null || a . tasks . size > b . tasks . size ? b : a ) , null as ITaskWorker | null ) ! ; } const r = { worker : new Worker ( this . blob ) , tasks : new Set < number > ( ) , refs : new Set ( ) , index : this . workers . length , } ; this . workers . push ( r ) ; return r ; } private finishedTask ( ) { if ( this . cleanUpWorkerTimer === - 1 && this . workers . length > MIN_WORKER_THREADS ) { this . cleanUpWorkerTimer = setTimeout ( this . cleanUpWorker , THREAD_CLEANUP_TIME ) as unknown as number ; } } pushStats ( type , args , refData , data , refIndices ? , indices ? ) ; pushStats ( type , args , refData , data , refIndices ? , indices ? ) ; pushStats ( type , args , refData , data , refIndices ? , indices ? ) ; pushStats ( type , args , refData , data , refIndices ? , indices ? ) ; pushStats ( type , args , refData , data , refIndices ? , indices ? ) ; pushStats ( type , args , refData , data , refIndices ? , indices ? ) { return new Promise ( ( resolve ) => { const uid = this . workerTaskCounter ++ ; const { worker , tasks , refs } = this . checkOutWorker ( ) ; const receiver = ( msg ) => { const r = msg . data as IWorkerMessage ; if ( r . uid !== uid || r . type !== type ) { return ; } worker . removeEventListener ( 'message' , receiver ) ; tasks . delete ( uid ) ; this . finishedTask ( ) ; resolve ( ( r as any ) . stats ) ; } ; worker . addEventListener ( 'message' , receiver ) ; tasks . add ( uid ) ; const msg = Object . assign ( { type , uid , refData , refIndices : refIndices || null , } , args ) ; if ( ! refData || ! refs . has ( refData ) ) { msg . data = data ; if ( refData ) { refs . add ( refData ) ; } } if ( indices && ( ! refIndices || ! refs . has ( refIndices ) ) ) { msg . indices = indices ! ; if ( refIndices ) { refs . add ( refIndices ) ; } } worker . postMessage ( msg ) ; } ) ; } push < M , R > ( type , args , transferAbles ) ; push < M , R , T > ( type , args , transferAbles , toResult ) ; push < M , R , T > ( type , args , transferAbles , toResult ? ) { return new Promise < T > ( ( resolve ) => { const uid = this . workerTaskCounter ++ ; const { worker , tasks } = this . checkOutWorker ( ) ; const receiver = ( msg ) => { const r = msg . data as IWorkerMessage ; if ( r . uid !== uid || r . type !== type ) { return ; } worker . removeEventListener ( 'message' , receiver ) ; tasks . delete ( uid ) ; this . finishedTask ( ) ; resolve ( toResult ? toResult ( r as any ) : ( r as any ) ) ; } ; worker . addEventListener ( 'message' , receiver ) ; tasks . add ( uid ) ; const msg = Object . assign ( { type , uid , } , args ) ; worker . postMessage ( msg , transferAbles ) ; } ) ; } setRef ( ref , data ) { for ( const w of this . workers ) { w . refs . add ( ref ) ; } this . broadCast ( 'setRef' , { ref , data , } ) ; } deleteRef ( ref , startsWith = false ) { const uid = this . workerTaskCounter ++ ; const msg = { type : 'deleteRef' , uid , ref , startsWith , } ; for ( const w of this . workers ) { w . worker . postMessage ( msg ) ; if ( ! startsWith ) { w . refs . delete ( ref ) ; continue ; } for ( const r of Array . from ( w . refs ) ) { if ( r . startsWith ( ref ) ) { w . refs . delete ( r ) ; } } } } deleteRefs ( ) { const uid = this . workerTaskCounter ++ ; const msg = { type : 'deleteRef' , uid , ref : '' , startsWith : true , } ; for ( const w of this . workers ) { w . worker . postMessage ( msg ) ; w . refs . clear ( ) ; } } broadCast < T > ( type , args ) { const uid = this . workerTaskCounter ++ ; const msg = Object . assign ( { type , uid , } , args ) ; for ( const w of this . workers ) { w . worker . postMessage ( msg ) ; } } }