UNPKG

motion-dom

Version:
102 lines (100 loc) • 3.8 kB
function createRenderStep(runNextFrame) { /** * We create and reuse two queues, one to queue jobs for the current frame * and one for the next. We reuse to avoid triggering GC after x frames. */ let thisFrame = new Set(); let nextFrame = new Set(); /** * Track whether we're currently processing jobs in this step. This way * we can decide whether to schedule new jobs for this frame or next. */ let isProcessing = false; let flushNextFrame = false; /** * The callback currently running. If it schedules itself with `immediate` * it's left in place, so as before it doesn't run again this step. */ let running; /** * A set of processes which were marked keepAlive when scheduled. * A keepAlive process is always also held by a frame queue until * it's cancelled, so a Set has the same lifetime semantics as a * WeakSet here while being considerably faster to query every frame. */ const toKeepAlive = new Set(); let latestFrameData = { delta: 0.0, timestamp: 0.0, isProcessing: false, }; function triggerCallback(callback) { if (toKeepAlive.has(callback)) { nextFrame.add(callback); runNextFrame(); } (running = callback)(latestFrameData); } const step = { /** * Schedule a process to run on the next frame. */ schedule: (callback, keepAlive = false, immediate = false) => { const addToCurrentFrame = immediate && isProcessing; const queue = addToCurrentFrame ? thisFrame : nextFrame; if (keepAlive) toKeepAlive.add(callback); /** * A callback scheduled with `immediate` during processing may * already have run this step (for instance, a value it reads was * changed by a later callback). Re-adding moves it to the end of * the queue, so it runs again. The running callback is excluded, * so one that re-schedules itself can't loop forever. */ addToCurrentFrame && callback !== running && queue.delete(callback); queue.add(callback); return callback; }, /** * Cancel the provided callback from running on the next frame. */ cancel: (callback) => { nextFrame.delete(callback); toKeepAlive.delete(callback); }, /** * Execute all schedule callbacks. */ process: (frameData) => { latestFrameData = frameData; /** * If we're already processing we've probably been triggered by a flushSync * inside an existing process. Instead of executing, mark flushNextFrame * as true and ensure we flush the following frame at the end of this one. */ if (isProcessing) { flushNextFrame = true; return; } isProcessing = true; // Swap this frame and the next to avoid GC const prevFrame = thisFrame; thisFrame = nextFrame; nextFrame = prevFrame; // Execute this frame thisFrame.forEach(triggerCallback); // Clear the frame so no callbacks remain. This is to avoid // memory leaks should this render step not run for a while. thisFrame.clear(); running = undefined; isProcessing = false; if (flushNextFrame) { flushNextFrame = false; step.process(frameData); } }, }; return step; } export { createRenderStep }; //# sourceMappingURL=render-step.mjs.map