Source: timers/SerialTimer.js

import { makeFastTick } from 'virtjs/devices/timers/utils';

let HANDLER_FN_SIZE = 31;
let HANDLER_FN_PATTERN = 0x7FFFFFFF;

export class SerialTimer {

    /**
     * A SerialTimer is a synchronous timer device. You can use it to run your emulator synchronously (ie. blocking the main thread). It also has the ability to only run a finite number of ticks before returning, which is quite valuable when debugging engines.
     *
     * @constructor
     * @implements {Timer}
     */

    constructor() {

        this.running = false;
        this.nested = false;

        this.queues = [ [ ], [ ] ];
        this.activeQueueIndex = 0;

    }

    nextTick(callback) {

        let activeQueueIndex = this.activeQueueIndex;
        let queue = this.queues[activeQueueIndex];
        let callbackIndex = queue.length;

        queue.push(callback);

        return activeQueueIndex << HANDLER_FN_SIZE | callbackIndex;

    }

    cancelTick(handler) {

        let activeQueueIndex = handler >>> HANDLER_FN_SIZE;
        let callbackIndex = handler & HANDLER_FN_PATTERN;

        this.queues[activeQueueIndex][callbackIndex] = null;

    }

    start(beginning, ending) {

        if (this.running)
            throw new Error(`You can't start a timer that is already running`);

        if (this.nested)
            throw new Error(`You can't start a timer from its callbacks - use resume instead`);

        let fastTick = makeFastTick(beginning, ending, () => {
            this.one();
        });

        this.running = true;
        this.nested = true;

        while (this.running)
            fastTick();

        this.nested = false;

    }

    resume() {

        if (!this.nested)
            throw new Error(`You can't resume a timer from anywhere else than its callbacks - use start instead`);

        if (this.running)
            return;

        this.running = true;

    }

    stop() {

        if (!this.running)
            return;

        this.running = false;

    }

    /**
     * Start the emulator. Run a single cycle then exit.
     */

    one() {

        let activeQueueIndex = this.activeQueueIndex;
        this.activeQueueIndex = activeQueueIndex ^ 1;

        let queue = this.queues[activeQueueIndex];

        for (let t = 0, T = queue.length; t < T; ++t)
            queue[t] && queue[t]();

        queue.length = 0;

    }

}