module xmcl export class CancelledError extends Error { constructor ( ) { super ( "Cancelled" ) ; } } /**
 * The collection of errors happened during a parallel process
 */ export class MultipleError extends Error { constructor ( readonly errors , message ? ) { super ( message ) ; } } export enum TaskState { Idle , Running , Cancelled , Paused , Succeed , Failed } export interface Task < T = any > { readonly id : number ; readonly name : string ; readonly param : Record < string , any > ; readonly progress : number ; readonly total : number ; readonly from : string | undefined ; readonly to : string | undefined ; readonly path : string ; readonly isCancelled : boolean ; readonly isPaused : boolean ; readonly isDone : boolean ; readonly isRunning : boolean ; readonly state : TaskState ; readonly context : TaskContext | undefined ; readonly parent : Task < any > | undefined ; pause ( ) : Promise < void > ; resume ( ) : Promise < void > ; cancel ( ) : Promise < void > ; start ( context ? , parent ? ) : void ; wait ( ) : Promise < T > ; startAndWait ( context ? , parent ? ) : Promise < T > ; onChildUpdate ( chunkSize ) : void ; map < N > ( transform ) : Task < N > ; setName ( name , param ? ) : this ; } export interface Transform < T , N > { ( this , value ) : N ; } export interface TaskContext { fork ? ( task ) : number ; onStart ? ( task ) : void ; onUpdate ? ( task , chunkSize ) : void ; onFailed ? ( task , error ) : void ; onSucceed ? ( task , result ) : void ; onPaused ? ( task ) : void ; onResumed ? ( task ) : void ; onCancelled ? ( task ) : void ; } export function createFork ( ) { let id = 0 ; return ( ) => id ++ ; } export abstract class BaseTask < T > implements Task < T > { protected _state : TaskState = TaskState . Idle ; protected _promise : Promise < T > = new Promise ( ( resolve , reject ) => { this . resolve = resolve ; this . reject = reject ; } ) ; protected resolve ! : ( value : T ) => void ; protected reject ! : ( err : any ) => void ; protected _from : string | undefined ; protected _to : string | undefined ; protected _progress = 0 ; protected _total = - 1 ; protected _path : string = "" ; protected _id : number = 0 ; parent : Task < any > | undefined ; context : TaskContext = { } ; name : string = "" ; param : object = { } ; protected resultOrError : T | any ; setName ( name , param ? ) { this . name = name ; this . param = param || this . param ; return this ; } get ( ) { if ( this . state === TaskState . Succeed ) { return this . resultOrError ; } else if ( this . state === TaskState . Failed ) { throw this . resultOrError ; } } get id ( ) { return this . _id ; } get path ( ) { return this . _path ; } get progress ( ) { return this . _progress ; } get total ( ) { return this . _total ; } get to ( ) { return this . _to ; } get from ( ) { return this . _from ; } get state ( ) { return this . _state ; } get isCancelled ( ) { return this . _state === TaskState . Cancelled ; } get isPaused ( ) { return this . _state === TaskState . Paused ; } get isDone ( ) { return this . _state === TaskState . Succeed ; } get isRunning ( ) { return this . _state === TaskState . Running ; } async pause ( ) { if ( this . _state !== TaskState . Running ) { return ; } this . _state = TaskState . Paused ; await this . pauseTask ( ) . then ( ( ) => { this . context . onPaused ?. ( this ) ; } ) ; } async resume ( ) { if ( this . _state !== TaskState . Paused ) { return ; } this . _state = TaskState . Running ; await this . resumeTask ( ) . then ( ( ) => { this . context . onResumed ?. ( this ) ; } ) ; } async cancel ( ) { if ( this . state !== TaskState . Running && this . state !== TaskState . Idle ) { return ; } this . _state = TaskState . Cancelled ; await this . cancelTask ( ) . then ( ( ) => { this . context . onCancelled ?. ( this ) ; } ) ; } wait ( ) { return this . _promise ; } start ( context ? , parent ? ) { if ( this . _state === TaskState . Cancelled ) { throw new CancelledError ( ) ; } if ( this . _state !== TaskState . Idle ) { return ; } if ( context ) { Object . assign ( this . context , context ) ; } if ( ! this . context . fork ) { this . context . fork = createFork ( ) ; } this . parent = parent ; this . _state = TaskState . Running ; this . _id = this . context . fork ! ( this ) ; this . _path = parent ? `${ parent . path }.${ this . name }` : this . name ; this . context . onStart ?. ( this ) ; this . runTask ( ) . then ( ( value ) => { this . resolve ( value ) ; this . _state = TaskState . Succeed ; this . resultOrError = value ; this . context . onSucceed ?. ( this , value ) ; } , ( error ) => { this . reject ( error ) ; this . resultOrError = error ; if ( this . state !== TaskState . Cancelled ) { this . _state = TaskState . Failed ; this . context . onFailed ?. ( this , error ) ; } } ) ; } startAndWait ( context ? , parent ? ) { this . start ( context , parent ) ; return this . wait ( ) ; } protected update ( chunkSize ) { this . context . onUpdate ?. ( this , chunkSize ) ; this . parent ?. onChildUpdate ( chunkSize ) ; } onChildUpdate ( chunkSize ) { } protected abstract runTask ( ) ; protected abstract cancelTask ( ) ; protected abstract pauseTask ( ) ; protected abstract resumeTask ( ) ; map < N > ( transform ) { const copy = Object . create ( this ) ; const wait = copy . wait ; const startAndWait = copy . startAndWait ; copy . wait = function ( this ) { return wait . bind ( this ) ( ) . then ( ( r ) => transform . bind ( this ) ( r ) ) ; } ; copy . startAndWait = function ( this , context ? , parent ? ) { return startAndWait . bind ( this ) ( context , parent ) . then ( ( r ) => transform . bind ( this ) ( r ) ) ; } ; return copy ; } } export abstract class AbortableTask < T > extends BaseTask < T > { protected _pausing : Promise < void > = Promise . resolve ( ) ; protected _unpause = ( ) => { } ; protected _onAborted = ( ) => { } ; protected _onResume = ( ) => { } ; protected abstract process ( ) ; protected abstract abort ( isCancelled ) ; protected abstract isAbortedError ( e ) ; protected cancelTask ( ) { this . abort ( true ) ; return new Promise ( ( resolve ) => { this . _onAborted = resolve ; } ) ; } protected pauseTask ( ) { this . _pausing = new Promise ( ( resolve ) => { this . _unpause = resolve ; } ) ; this . abort ( false ) ; return new Promise ( ( resolve ) => { this . _onAborted = resolve ; } ) ; } protected resumeTask ( ) { this . _unpause ( ) ; return new Promise ( ( resolve ) => { this . _onResume = resolve ; } ) ; } protected async runTask ( ) { while ( true ) { try { if ( this . state === TaskState . Cancelled ) { throw new CancelledError ( ) ; } await this . _pausing ; this . _onResume ( ) ; const result = await this . process ( ) ; return result ; } catch ( e ) { if ( this . _state === TaskState . Paused && this . isAbortedError ( e ) ) { this . _onAborted ( ) ; continue ; } if ( this . _state === TaskState . Cancelled ) { this . _onAborted ( ) ; } throw e ; } } } } export abstract class TaskGroup < T > extends BaseTask < T > { protected children : Task < any > [ ] = [ ] ; onChildUpdate ( chunkSize ) { let total = 0 ; let progress = 0 ; for ( const task of this . children ) { progress += task . progress ; total += task . total ; } this . _total = total ; this . _progress = progress ; this . update ( chunkSize ) ; } ; protected async cancelTask ( ) { await Promise . all ( this . children . map ( ( task ) => task . cancel ( ) ) ) ; } protected async pauseTask ( ) { await Promise . all ( this . children . map ( ( task ) => task . pause ( ) ) ) ; } protected async resumeTask ( ) { await Promise . all ( this . children . map ( ( task ) => task . resume ( ) ) ) ; } async all < Z , T extends Task < Z > > ( tasks , { throwErrorImmediately , getErrorMessage } = { throwErrorImmediately : true , getErrorMessage : ( errors ) => "" } ) { const errors = [ ] ; const promises = [ ] ; for ( const task of tasks ) { this . children . push ( task ) ; const promise = task . startAndWait ( this . context , this ) . catch ( ( error ) => { if ( throwErrorImmediately || error instanceof CancelledError ) { throw error ; } errors . push ( error ) ; } ) ; promises . push ( promise ) ; } try { const result = await Promise . all ( promises ) ; if ( errors . length > 0 ) { throw new MultipleError ( errors , getErrorMessage ?. ( errors ) ) ; } return result as Z [ ] ; } catch ( e ) { if ( throwErrorImmediately ) { for ( const task of tasks ) { if ( task . isRunning ) { task . cancel ( ) . catch ( ( e ) => { errors . push ( e ) ; } ) ; } } } throw e ; } } } export type TaskExecutor < T > = ( this : TaskRoutine < any > ) => Promise < T > | T ; export class TaskRoutine < T > extends TaskGroup < T > { constructor ( name , readonly executor , param = { } ) { super ( ) ; this . setName ( name , param ) ; } concat < T > ( task ) { try { const result = task . executor . bind ( this ) ( ) ; if ( result instanceof Promise ) { return result ; } return Promise . resolve ( result ) ; } catch ( e ) { return Promise . reject ( e ) ; } } /**
     * Yield a new child task to this routine
     */ yield < T > ( task ) { if ( this . state !== TaskState . Running ) { throw new Error ( "IllegalState" ) ; } this . children . push ( task ) ; return task . startAndWait ( this . context , this ) ; } protected runTask ( ) { try { const result = this . executor . bind ( this ) ( ) ; if ( result instanceof Promise ) { return result ; } return Promise . resolve ( result ) ; } catch ( e ) { return Promise . reject ( e ) ; } } } export function task < T > ( name , executor , param = { } ) { return new TaskRoutine < T > ( name , executor , param ) ; }