import { Vertex } from './Vertex' ; import * as Collections from 'typescript-collections' ; export class Entry { constructor ( rank , action ) { this . rank = rank ; this . action = action ; this . seq = Entry . nextSeq ++ ; } private static nextSeq : number = 0 ; rank : Vertex ; action : ( ) => void ; seq : number ; toString ( ) { return this . seq . toString ( ) ; } } export class Transaction { public static currentTransaction : Transaction = null ; private static onStartHooks : ( ( ) => void ) [ ] = [ ] ; private static runningOnStartHooks : boolean = false ; constructor ( ) { } inCallback : number = 0 ; private toRegen : boolean = false ; requestRegen ( ) { this . toRegen = true ; } prioritizedQ : Collections . PriorityQueue < Entry > = new Collections . PriorityQueue < Entry > ( ( a , b ) => { if ( a . rank . rank < b . rank . rank ) return 1 ; if ( a . rank . rank > b . rank . rank ) return - 1 ; if ( a . seq < b . seq ) return 1 ; if ( a . seq > b . seq ) return - 1 ; return 0 ; } ) ; private entries : Collections . Set < Entry > = new Collections . Set < Entry > ( ( a ) => a . toString ( ) ) ; private sampleQ : Array < ( ) => void > = [ ] ; private lastQ : Array < ( ) => void > = [ ] ; private postQ : Array < ( ) => void > = null ; private static collectCyclesAtEnd : boolean = false ; prioritized ( target , action ) { const e = new Entry ( target , action ) ; this . prioritizedQ . enqueue ( e ) ; this . entries . add ( e ) ; } sample ( h ) { this . sampleQ . push ( h ) ; } last ( h ) { this . lastQ . push ( h ) ; } public static _collectCyclesAtEnd ( ) { Transaction . run ( ( ) => Transaction . collectCyclesAtEnd = true ) ; } /**
     * Add an action to run after all last() actions.
     */ post ( childIx , action ) { if ( this . postQ == null ) this . postQ = [ ] ; while ( this . postQ . length <= childIx ) this . postQ . push ( null ) ; const existing = this . postQ [ childIx ] , neu = existing === null ? action : ( ) => { existing ( ) ; action ( ) ; } ; this . postQ [ childIx ] = neu ; } private checkRegen ( ) { if ( this . toRegen ) { this . toRegen = false ; this . prioritizedQ . clear ( ) ; const es = this . entries . toArray ( ) ; for ( let i = 0 ; i < es . length ; i ++ ) this . prioritizedQ . enqueue ( es [ i ] ) ; } } public isActive ( ) { return Transaction . currentTransaction ? true : false ; } close ( ) { while ( true ) { while ( true ) { this . checkRegen ( ) ; if ( this . prioritizedQ . isEmpty ( ) ) break ; const e = this . prioritizedQ . dequeue ( ) ; this . entries . remove ( e ) ; e . action ( ) ; } const sq = this . sampleQ ; this . sampleQ = [ ] ; for ( let i = 0 ; i < sq . length ; i ++ ) sq [ i ] ( ) ; if ( this . prioritizedQ . isEmpty ( ) && this . sampleQ . length < 1 ) break ; } for ( let i = 0 ; i < this . lastQ . length ; i ++ ) this . lastQ [ i ] ( ) ; this . lastQ = [ ] ; if ( this . postQ != null ) { for ( let i = 0 ; i < this . postQ . length ; i ++ ) { if ( this . postQ [ i ] != null ) { const parent = Transaction . currentTransaction ; try { if ( i > 0 ) { Transaction . currentTransaction = new Transaction ( ) ; try { this . postQ [ i ] ( ) ; Transaction . currentTransaction . close ( ) ; } catch ( err ) { Transaction . currentTransaction . close ( ) ; throw err ; } } else { Transaction . currentTransaction = null ; this . postQ [ i ] ( ) ; } Transaction . currentTransaction = parent ; } catch ( err ) { Transaction . currentTransaction = parent ; throw err ; } } } this . postQ = null ; } } /**
     * Add a runnable that will be executed whenever a transaction is started.
     * That runnable may start transactions itself, which will not cause the
     * hooks to be run recursively.
     *
     * The main use case of this is the implementation of a time/alarm system.
     */ static onStart ( r ) { Transaction . onStartHooks . push ( r ) ; } public static run < A > ( f ) { const transWas = Transaction . currentTransaction ; if ( transWas === null ) { if ( ! Transaction . runningOnStartHooks ) { Transaction . runningOnStartHooks = true ; try { for ( let i = 0 ; i < Transaction . onStartHooks . length ; i ++ ) Transaction . onStartHooks [ i ] ( ) ; } finally { Transaction . runningOnStartHooks = false ; } } Transaction . currentTransaction = new Transaction ( ) ; } try { const a = f ( ) ; if ( transWas === null ) { Transaction . currentTransaction . close ( ) ; Transaction . currentTransaction = null ; if ( Transaction . collectCyclesAtEnd ) { Vertex . collectCycles ( ) ; Transaction . collectCyclesAtEnd = false ; } } return a ; } catch ( err ) { if ( transWas === null ) { Transaction . currentTransaction . close ( ) ; Transaction . currentTransaction = null ; } throw err ; } } }