import { EventSlot } from '../events/EventSlot'; import { AnimationFrameEvent } from './AnimationFrameEvent'; import { sleep } from './sleep'; export type Idle = "Idle"; export type Running = "Running"; export type Stopping = "Stopping"; export type OnAnimationFrameStatus = Idle | Running | Stopping; export class OnAnimationFrame extends EventSlot { static readonly Idle:Idle = "Idle"; static readonly Running:Running = "Running"; static readonly Stopping:Stopping = "Stopping"; #status:OnAnimationFrameStatus = OnAnimationFrame.Idle; get status(){ return this.#status; } #stopFlag = false; #updater:()=>void = null; #animationFrameEvent = new AnimationFrameEvent(); constructor() { super(); this.#animationFrameEvent.onAnimationFrame = this; } doUntil( condition:()=>boolean, callback:( e:AnimationFrameEvent )=>void ) { let conditionalCallback = ( e:AnimationFrameEvent )=> { callback( e ); if ( ! condition() ) { this.removeListener( conditionalCallback ); } } this.addListener( conditionalCallback ); } doOnceWhen( condition:()=>boolean, callback:( e:AnimationFrameEvent )=>void ) { let conditionalCallback = ( e:AnimationFrameEvent )=> { if ( ! condition() ) { return; } callback( e ); this.removeListener( conditionalCallback ); } this.addListener( conditionalCallback ); } run() { if ( OnAnimationFrame.Running === this.#status ) { return; } this._schedulRunning(); } private async _schedulRunning() { if ( ! this.#updater ) { this._createUpdaterFunction(); } if ( OnAnimationFrame.Idle === this.#status ) { this._startUpdaterLoop(); return; } let currentState = this.#status as OnAnimationFrameStatus; let maxTries = 1000; while ( OnAnimationFrame.Idle !== currentState ) { maxTries--; if ( maxTries <= 0 ) { return Promise.reject(); } await sleep( 1 ); currentState = this.#status as OnAnimationFrameStatus; if ( OnAnimationFrame.Idle === currentState ) { this._startUpdaterLoop(); return; } } } private _startUpdaterLoop() { this.#stopFlag = false; this.#status = OnAnimationFrame.Running; this.#updater(); } private _createUpdaterFunction() { this.#updater = ( ) => { this._update(); if ( this.#stopFlag ) { this.#stopFlag = false; this.#status = OnAnimationFrame.Idle; } else { requestAnimationFrame( this.#updater ); } }; } private _update() { this.#animationFrameEvent.update(); this.dispatch( this.#animationFrameEvent ); } stop() { this.#stopFlag = true; } }