import { Component , updateComponent } from "./component" ; import { ComponentFlags } from "./misc" ; import { VNode } from "./vnode" ; export type SchedulerTask = ( ) => void ; /**
 * Scheduler flags.
 */ const enum SchedulerFlags { MicrotaskPending = 1 , MacrotaskPending = 1 << 1 , FrametaskPending = 1 << 2 , EnabledMounting = 1 << 3 } const enum FrameTasksGroupFlags { Component = 1 , Write = 1 << 1 , Read = 1 << 2 , After = 1 << 3 , RWLock = 1 << 4 } final export class FrameTasksGroup { /**
     * See `FrameTasksGroupFlags` for details.
     */ _flags : number ; /**
     * Array of component arrays indexed by their depth.
     */ _componentTasks : Array < Component < any , any > [ ] | null > ; /**
     * Write DOM task queue.
     */ _writeTasks : SchedulerTask [ ] | null ; /**
     * Read DOM task queue.
     */ _readTasks : SchedulerTask [ ] | null ; /**
     * Tasks that should be executed when all other tasks are finished.
     */ _afterTasks : SchedulerTask [ ] | null ; /**
     * Element that should be focused.
     */ _focus : Element | VNode | null ; constructor ( ) { this . _flags = 0 ; this . _componentTasks = [ ] ; this . _writeTasks = null ; this . _readTasks = null ; this . _afterTasks = null ; this . _focus = null ; } /**
     * Add Component to the components queue.
     */ updateComponent ( component ) { if ( "<@KIVI_DEBUG@>" as string !== "DEBUG_DISABLED" ) { if ( ( this . _flags & FrameTasksGroupFlags . RWLock ) !== 0 ) { throw new Error ( "Failed to add update component task to the current frame, current frame is locked for read" + " and write tasks." ) ; } } if ( ( component . flags & ComponentFlags . InUpdateQueue ) === 0 ) { component . flags |= ComponentFlags . InUpdateQueue ; const priority = component . depth ; this . _flags |= FrameTasksGroupFlags . Component ; while ( priority >= this . _componentTasks . length ) { this . _componentTasks . push ( null ) ; } const group = this . _componentTasks [ priority ] ; if ( group === null ) { this . _componentTasks [ priority ] = [ component ] ; } else { group . push ( component ) ; } } } /**
     * Add new task to the write DOM task queue.
     */ write ( callback ) { if ( "<@KIVI_DEBUG@>" as string !== "DEBUG_DISABLED" ) { if ( ( this . _flags & FrameTasksGroupFlags . RWLock ) !== 0 ) { throw new Error ( "Failed to add update component task to the current frame, current frame is locked for read" + " and write tasks." ) ; } } this . _flags |= FrameTasksGroupFlags . Write ; if ( this . _writeTasks === null ) { this . _writeTasks = [ ] ; } this . _writeTasks . push ( callback ) ; } /**
     * Add new task to the read DOM task queue.
     */ read ( callback ) { if ( "<@KIVI_DEBUG@>" as string !== "DEBUG_DISABLED" ) { if ( ( this . _flags & FrameTasksGroupFlags . RWLock ) !== 0 ) { throw new Error ( "Failed to add update component task to the current frame, current frame is locked for read" + " and write tasks." ) ; } } this . _flags |= FrameTasksGroupFlags . Read ; if ( this . _readTasks === null ) { this . _readTasks = [ ] ; } this . _readTasks . push ( callback ) ; } /**
     * Add new task to the task queue that will execute tasks when all DOM tasks are finished.
     */ after ( callback ) { this . _flags |= FrameTasksGroupFlags . After ; if ( this . _afterTasks === null ) { this . _afterTasks = [ ] ; } this . _afterTasks . push ( callback ) ; } /**
     * Set a focus on an element when all DOM tasks are finished.
     */ focus ( node ) { this . _focus = node ; } /**
     * Place a lock on adding new read and write task.
     *
     * Works in DEBUG mode only.
     */ _rwLock ( ) { if ( "<@KIVI_DEBUG@>" as string !== "DEBUG_DISABLED" ) { this . _flags |= FrameTasksGroupFlags . RWLock ; } } /**
     * Remove a lock from adding new read and write tasks.
     *
     * Works in DEBUG mode only.
     */ _rwUnlock ( ) { if ( "<@KIVI_DEBUG@>" as string !== "DEBUG_DISABLED" ) { this . _flags &= ~ FrameTasksGroupFlags . RWLock ; } } } /**
 * Global scheduler variables.
 *
 * Scheduler supports animation frame tasks, macrotasks and microtasks.
 *
 * Animation frame tasks will be executed in batches, switching between write and read tasks until there
 * are no tasks left. Write tasks are sorted by their priority, tasks with the lowest priority value are
 * executed first, so the lowest depth of the Component in the components tree has highest priority.
 *
 * Scheduler also have monotonically increasing internal clock, it increments each time scheduler goes
 * from one animation frame to another, or starts executing macrotasks or microtasks.
 */ const scheduler = { /**
     * See `SchedulerFlags` for details.
     */ flags : 0 , clock : 0 , time : 0 , microtasks : [ ] as SchedulerTask [ ] , macrotasks : [ ] as SchedulerTask [ ] , currentFrame : new FrameTasksGroup ( ) , nextFrame : new FrameTasksGroup ( ) , updateComponents : [ ] as Component < any , any > [ ] , microtaskNode : document . createTextNode ( "" ) , microtaskToggle : 0 , macrotaskMessage : "__kivi" + Math . random ( ) , } ; const microtaskObserver = new MutationObserver ( runMicrotasks ) ; microtaskObserver . observe ( scheduler . microtaskNode , { characterData : true } ) ; window . addEventListener ( "message" , handleWindowMessage ) ; scheduler . currentFrame . _rwLock ( ) ; /**
 * Returns current monotonically increasing clock.
 */ export function clock ( ) { return scheduler . clock ; } function requestMicrotaskExecution ( ) { if ( ( scheduler . flags & SchedulerFlags . MicrotaskPending ) === 0 ) { scheduler . flags |= SchedulerFlags . MicrotaskPending ; scheduler . microtaskToggle ^= 1 ; scheduler . microtaskNode . nodeValue = scheduler . microtaskToggle ? "1" : "0" ; } } function requestMacrotaskExecution ( ) { if ( ( scheduler . flags & SchedulerFlags . MacrotaskPending ) === 0 ) { scheduler . flags |= SchedulerFlags . MacrotaskPending ; window . postMessage ( scheduler . macrotaskMessage , "*" ) ; } } function requestNextFrame ( ) { if ( ( scheduler . flags & SchedulerFlags . FrametaskPending ) === 0 ) { scheduler . flags |= SchedulerFlags . FrametaskPending ; requestAnimationFrame ( handleNextFrame ) ; } } function handleWindowMessage ( e ) { if ( e . source === window && e . data === scheduler . macrotaskMessage ) { runMacrotasks ( ) ; } } function handleNextFrame ( t ) { const updateComponents = scheduler . updateComponents ; let tasks ; let i ; let j ; scheduler . flags &= ~ SchedulerFlags . FrametaskPending ; scheduler . time = Date . now ( ) ; const frame = scheduler . nextFrame ; scheduler . nextFrame = scheduler . currentFrame ; scheduler . currentFrame = frame ; scheduler . currentFrame . _rwUnlock ( ) ; scheduler . nextFrame . _rwUnlock ( ) ; for ( i = 0 ; i < updateComponents . length ; i ++ ) { updateComponents [ i ] . flags |= ComponentFlags . Dirty ; } do { while ( ( frame . _flags & FrameTasksGroupFlags . Read ) !== 0 ) { frame . _flags &= ~ FrameTasksGroupFlags . Read ; tasks = frame . _readTasks ! ; frame . _readTasks = null ; for ( i = 0 ; i < tasks . length ; i ++ ) { tasks [ i ] ( ) ; } } while ( ( frame . _flags & ( FrameTasksGroupFlags . Component | FrameTasksGroupFlags . Write ) ) !== 0 ) { if ( ( frame . _flags & FrameTasksGroupFlags . Component ) !== 0 ) { frame . _flags &= ~ FrameTasksGroupFlags . Component ; const componentGroups = frame . _componentTasks ; for ( i = 0 ; i < componentGroups . length ; i ++ ) { const componentGroup = componentGroups [ i ] ; if ( componentGroup !== null ) { componentGroups [ i ] = null ; for ( j = 0 ; j < componentGroup . length ; j ++ ) { updateComponent ( componentGroup [ j ] ) ; } } } } if ( ( frame . _flags & FrameTasksGroupFlags . Write ) !== 0 ) { frame . _flags &= ~ FrameTasksGroupFlags . Write ; tasks = frame . _writeTasks ! ; frame . _writeTasks = null ; for ( i = 0 ; i < tasks . length ; i ++ ) { tasks [ i ] ( ) ; } } } i = 0 ; j = updateComponents . length ; while ( i < j ) { const component = updateComponents [ i ++ ] ; if ( ( component . flags & ComponentFlags . UpdateEachFrame ) === 0 ) { component . flags &= ~ ComponentFlags . InUpdateEachFrameQueue ; if ( i === j ) { updateComponents . pop ( ) ; } else { updateComponents [ -- i ] = updateComponents . pop ( ) ! ; } } else { updateComponent ( component ) ; } } } while ( ( frame . _flags & ( FrameTasksGroupFlags . Component | FrameTasksGroupFlags . Write | FrameTasksGroupFlags . Read ) ) !== 0 ) ; scheduler . currentFrame . _rwLock ( ) ; while ( ( frame . _flags & FrameTasksGroupFlags . After ) !== 0 ) { frame . _flags &= ~ FrameTasksGroupFlags . After ; tasks = frame . _afterTasks ! ; frame . _afterTasks = null ; for ( i = 0 ; i < tasks . length ; i ++ ) { tasks [ i ] ( ) ; } } if ( frame . _focus !== null ) { if ( frame . _focus . constructor === VNode ) { ( ( frame . _focus as VNode ) . ref as HTMLElement ) . focus ( ) ; } else { ( frame . _focus as HTMLElement ) . focus ( ) ; } frame . _focus = null ; } if ( updateComponents . length > 0 ) { requestNextFrame ( ) ; } scheduler . clock ++ ; } function runMicrotasks ( ) { scheduler . time = Date . now ( ) ; while ( scheduler . microtasks . length > 0 ) { const tasks = scheduler . microtasks ; scheduler . microtasks = [ ] ; for ( let i = 0 ; i < tasks . length ; i ++ ) { tasks [ i ] ( ) ; } scheduler . clock ++ ; } scheduler . flags &= ~ SchedulerFlags . MicrotaskPending ; } function runMacrotasks ( ) { scheduler . flags &= ~ SchedulerFlags . MacrotaskPending ; scheduler . time = Date . now ( ) ; let tasks = scheduler . macrotasks ; scheduler . macrotasks = [ ] ; for ( let i = 0 ; i < tasks . length ; i ++ ) { tasks [ i ] ( ) ; } scheduler . clock ++ ; } /**
 * Add task to the microtask queue.
 */ export function scheduleMicrotask ( task ) { requestMicrotaskExecution ( ) ; scheduler . microtasks . push ( task ) ; } /**
 * Add task to the macrotask queue.
 */ export function scheduleMacrotask ( task ) { requestMacrotaskExecution ( ) ; scheduler . macrotasks . push ( task ) ; } /**
 * Get task list for the current frame.
 */ export function currentFrame ( ) { return scheduler . currentFrame ; } /**
 * Get task list for the next frame.
 */ export function nextFrame ( ) { requestNextFrame ( ) ; return scheduler . nextFrame ; } /**
 * Add component to the list of components that should be updated each frame.
 */ export function startUpdateComponentEachFrame ( component ) { requestNextFrame ( ) ; scheduler . updateComponents . push ( component ) ; } /**
 * Returns true if UI state is in mounting phase.
 */ export function isMounting ( ) { return ( ( scheduler . flags & SchedulerFlags . EnabledMounting ) !== 0 ) ; } /**
 * Start UI mounting phase.
 */ export function startMounting ( ) { scheduler . flags |= SchedulerFlags . EnabledMounting ; } /**
 * Finish UI mounting phase.
 */ export function finishMounting ( ) { scheduler . flags &= ~ SchedulerFlags . EnabledMounting ; }