(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.framesync = {}));
}(this, (function (exports) { 'use strict';

    var defaultTimestep = (1 / 60) * 1000;
    var getCurrentTime = typeof performance !== "undefined"
        ? function () { return performance.now(); }
        : function () { return Date.now(); };
    var onNextFrame = typeof window !== "undefined"
        ? function (callback) {
            return window.requestAnimationFrame(callback);
        }
        : function (callback) {
            return setTimeout(function () { return callback(getCurrentTime()); }, defaultTimestep);
        };

    function createRenderStep(runNextFrame) {
        var toRun = [];
        var toRunNextFrame = [];
        var numToRun = 0;
        var isProcessing = false;
        var toKeepAlive = new WeakSet();
        var step = {
            schedule: function (callback, keepAlive, immediate) {
                if (keepAlive === void 0) { keepAlive = false; }
                if (immediate === void 0) { immediate = false; }
                var addToCurrentFrame = immediate && isProcessing;
                var buffer = addToCurrentFrame ? toRun : toRunNextFrame;
                if (keepAlive)
                    toKeepAlive.add(callback);
                if (buffer.indexOf(callback) === -1) {
                    buffer.push(callback);
                    if (addToCurrentFrame && isProcessing)
                        numToRun = toRun.length;
                }
                return callback;
            },
            cancel: function (callback) {
                var index = toRunNextFrame.indexOf(callback);
                if (index !== -1)
                    toRunNextFrame.splice(index, 1);
                toKeepAlive.delete(callback);
            },
            process: function (frameData) {
                var _a;
                isProcessing = true;
                _a = [toRunNextFrame, toRun], toRun = _a[0], toRunNextFrame = _a[1];
                toRunNextFrame.length = 0;
                numToRun = toRun.length;
                if (numToRun) {
                    for (var i = 0; i < numToRun; i++) {
                        var callback = toRun[i];
                        callback(frameData);
                        if (toKeepAlive.has(callback)) {
                            step.schedule(callback);
                            runNextFrame();
                        }
                    }
                }
                isProcessing = false;
            },
        };
        return step;
    }

    var maxElapsed = 40;
    var useDefaultElapsed = true;
    var runNextFrame = false;
    var isProcessing = false;
    var frame = {
        delta: 0,
        timestamp: 0,
    };
    var stepsOrder = [
        "read",
        "update",
        "preRender",
        "render",
        "postRender",
    ];
    var steps = stepsOrder.reduce(function (acc, key) {
        acc[key] = createRenderStep(function () { return (runNextFrame = true); });
        return acc;
    }, {});
    var sync = stepsOrder.reduce(function (acc, key) {
        var step = steps[key];
        acc[key] = function (process, keepAlive, immediate) {
            if (keepAlive === void 0) { keepAlive = false; }
            if (immediate === void 0) { immediate = false; }
            if (!runNextFrame)
                startLoop();
            return step.schedule(process, keepAlive, immediate);
        };
        return acc;
    }, {});
    var cancelSync = stepsOrder.reduce(function (acc, key) {
        acc[key] = steps[key].cancel;
        return acc;
    }, {});
    var flushSync = stepsOrder.reduce(function (acc, key) {
        acc[key] = function () { return steps[key].process(frame); };
        return acc;
    }, {});
    var processStep = function (stepId) { return steps[stepId].process(frame); };
    var processFrame = function (timestamp) {
        runNextFrame = false;
        frame.delta = useDefaultElapsed
            ? defaultTimestep
            : Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1);
        frame.timestamp = timestamp;
        isProcessing = true;
        stepsOrder.forEach(processStep);
        isProcessing = false;
        if (runNextFrame) {
            useDefaultElapsed = false;
            onNextFrame(processFrame);
        }
    };
    var startLoop = function () {
        runNextFrame = true;
        useDefaultElapsed = true;
        if (!isProcessing)
            onNextFrame(processFrame);
    };
    var getFrameData = function () { return frame; };

    exports.cancelSync = cancelSync;
    exports.default = sync;
    exports.flushSync = flushSync;
    exports.getFrameData = getFrameData;

    Object.defineProperty(exports, '__esModule', { value: true });

})));
