{"version":3,"file":"render-step.mjs","sources":["../../../src/frameloop/render-step.ts"],"sourcesContent":["import { FrameData, Process, Step } from \"./types\"\n\nexport function createRenderStep(runNextFrame: () => void): Step {\n    /**\n     * We create and reuse two queues, one to queue jobs for the current frame\n     * and one for the next. We reuse to avoid triggering GC after x frames.\n     */\n    let thisFrame = new Set<Process>()\n    let nextFrame = new Set<Process>()\n\n    /**\n     * Track whether we're currently processing jobs in this step. This way\n     * we can decide whether to schedule new jobs for this frame or next.\n     */\n    let isProcessing = false\n\n    let flushNextFrame = false\n\n    /**\n     * The callback currently running. If it schedules itself with `immediate`\n     * it's left in place, so as before it doesn't run again this step.\n     */\n    let running: Process | undefined\n\n    /**\n     * A set of processes which were marked keepAlive when scheduled.\n     * A keepAlive process is always also held by a frame queue until\n     * it's cancelled, so a Set has the same lifetime semantics as a\n     * WeakSet here while being considerably faster to query every frame.\n     */\n    const toKeepAlive = new Set<Process>()\n\n    let latestFrameData: FrameData = {\n        delta: 0.0,\n        timestamp: 0.0,\n        isProcessing: false,\n    }\n\n    function triggerCallback(callback: Process) {\n        if (toKeepAlive.has(callback)) {\n            nextFrame.add(callback)\n            runNextFrame()\n        }\n\n        ;(running = callback)(latestFrameData)\n    }\n\n    const step: Step = {\n        /**\n         * Schedule a process to run on the next frame.\n         */\n        schedule: (callback, keepAlive = false, immediate = false) => {\n            const addToCurrentFrame = immediate && isProcessing\n            const queue = addToCurrentFrame ? thisFrame : nextFrame\n\n            if (keepAlive) toKeepAlive.add(callback)\n\n            /**\n             * A callback scheduled with `immediate` during processing may\n             * already have run this step (for instance, a value it reads was\n             * changed by a later callback). Re-adding moves it to the end of\n             * the queue, so it runs again. The running callback is excluded,\n             * so one that re-schedules itself can't loop forever.\n             */\n            addToCurrentFrame && callback !== running && queue.delete(callback)\n\n            queue.add(callback)\n\n            return callback\n        },\n\n        /**\n         * Cancel the provided callback from running on the next frame.\n         */\n        cancel: (callback) => {\n            nextFrame.delete(callback)\n            toKeepAlive.delete(callback)\n        },\n\n        /**\n         * Execute all schedule callbacks.\n         */\n        process: (frameData) => {\n            latestFrameData = frameData\n\n            /**\n             * If we're already processing we've probably been triggered by a flushSync\n             * inside an existing process. Instead of executing, mark flushNextFrame\n             * as true and ensure we flush the following frame at the end of this one.\n             */\n            if (isProcessing) {\n                flushNextFrame = true\n                return\n            }\n\n            isProcessing = true\n\n            // Swap this frame and the next to avoid GC\n            const prevFrame = thisFrame\n            thisFrame = nextFrame\n            nextFrame = prevFrame\n\n            // Execute this frame\n            thisFrame.forEach(triggerCallback)\n\n            // Clear the frame so no callbacks remain. This is to avoid\n            // memory leaks should this render step not run for a while.\n            thisFrame.clear()\n            running = undefined\n\n            isProcessing = false\n\n            if (flushNextFrame) {\n                flushNextFrame = false\n                step.process(frameData)\n            }\n        },\n    }\n\n    return step\n}\n"],"names":[],"mappings":"AAEM,SAAU,gBAAgB,CAAC,YAAwB,EAAA;AACrD;;;AAGG;AACH,IAAA,IAAI,SAAS,GAAG,IAAI,GAAG,EAAW;AAClC,IAAA,IAAI,SAAS,GAAG,IAAI,GAAG,EAAW;AAElC;;;AAGG;IACH,IAAI,YAAY,GAAG,KAAK;IAExB,IAAI,cAAc,GAAG,KAAK;AAE1B;;;AAGG;AACH,IAAA,IAAI,OAA4B;AAEhC;;;;;AAKG;AACH,IAAA,MAAM,WAAW,GAAG,IAAI,GAAG,EAAW;AAEtC,IAAA,IAAI,eAAe,GAAc;AAC7B,QAAA,KAAK,EAAE,GAAG;AACV,QAAA,SAAS,EAAE,GAAG;AACd,QAAA,YAAY,EAAE,KAAK;KACtB;IAED,SAAS,eAAe,CAAC,QAAiB,EAAA;AACtC,QAAA,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;AAC3B,YAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AACvB,YAAA,YAAY,EAAE;QAClB;AAEC,QAAA,CAAC,OAAO,GAAG,QAAQ,EAAE,eAAe,CAAC;IAC1C;AAEA,IAAA,MAAM,IAAI,GAAS;AACf;;AAEG;AACH,QAAA,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,KAAI;AACzD,YAAA,MAAM,iBAAiB,GAAG,SAAS,IAAI,YAAY;YACnD,MAAM,KAAK,GAAG,iBAAiB,GAAG,SAAS,GAAG,SAAS;AAEvD,YAAA,IAAI,SAAS;AAAE,gBAAA,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;AAExC;;;;;;AAMG;YACH,iBAAiB,IAAI,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;AAEnE,YAAA,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEnB,YAAA,OAAO,QAAQ;QACnB,CAAC;AAED;;AAEG;AACH,QAAA,MAAM,EAAE,CAAC,QAAQ,KAAI;AACjB,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAC1B,YAAA,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChC,CAAC;AAED;;AAEG;AACH,QAAA,OAAO,EAAE,CAAC,SAAS,KAAI;YACnB,eAAe,GAAG,SAAS;AAE3B;;;;AAIG;YACH,IAAI,YAAY,EAAE;gBACd,cAAc,GAAG,IAAI;gBACrB;YACJ;YAEA,YAAY,GAAG,IAAI;;YAGnB,MAAM,SAAS,GAAG,SAAS;YAC3B,SAAS,GAAG,SAAS;YACrB,SAAS,GAAG,SAAS;;AAGrB,YAAA,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC;;;YAIlC,SAAS,CAAC,KAAK,EAAE;YACjB,OAAO,GAAG,SAAS;YAEnB,YAAY,GAAG,KAAK;YAEpB,IAAI,cAAc,EAAE;gBAChB,cAAc,GAAG,KAAK;AACtB,gBAAA,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;YAC3B;QACJ,CAAC;KACJ;AAED,IAAA,OAAO,IAAI;AACf;;;;"}