render-step.mjs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { statsBuffer } from '../stats/buffer.mjs';
  2. function createRenderStep(runNextFrame, stepName) {
  3. /**
  4. * We create and reuse two queues, one to queue jobs for the current frame
  5. * and one for the next. We reuse to avoid triggering GC after x frames.
  6. */
  7. let thisFrame = new Set();
  8. let nextFrame = new Set();
  9. /**
  10. * Track whether we're currently processing jobs in this step. This way
  11. * we can decide whether to schedule new jobs for this frame or next.
  12. */
  13. let isProcessing = false;
  14. let flushNextFrame = false;
  15. /**
  16. * A set of processes which were marked keepAlive when scheduled.
  17. */
  18. const toKeepAlive = new WeakSet();
  19. let latestFrameData = {
  20. delta: 0.0,
  21. timestamp: 0.0,
  22. isProcessing: false,
  23. };
  24. let numCalls = 0;
  25. function triggerCallback(callback) {
  26. if (toKeepAlive.has(callback)) {
  27. step.schedule(callback);
  28. runNextFrame();
  29. }
  30. numCalls++;
  31. callback(latestFrameData);
  32. }
  33. const step = {
  34. /**
  35. * Schedule a process to run on the next frame.
  36. */
  37. schedule: (callback, keepAlive = false, immediate = false) => {
  38. const addToCurrentFrame = immediate && isProcessing;
  39. const queue = addToCurrentFrame ? thisFrame : nextFrame;
  40. if (keepAlive)
  41. toKeepAlive.add(callback);
  42. if (!queue.has(callback))
  43. queue.add(callback);
  44. return callback;
  45. },
  46. /**
  47. * Cancel the provided callback from running on the next frame.
  48. */
  49. cancel: (callback) => {
  50. nextFrame.delete(callback);
  51. toKeepAlive.delete(callback);
  52. },
  53. /**
  54. * Execute all schedule callbacks.
  55. */
  56. process: (frameData) => {
  57. latestFrameData = frameData;
  58. /**
  59. * If we're already processing we've probably been triggered by a flushSync
  60. * inside an existing process. Instead of executing, mark flushNextFrame
  61. * as true and ensure we flush the following frame at the end of this one.
  62. */
  63. if (isProcessing) {
  64. flushNextFrame = true;
  65. return;
  66. }
  67. isProcessing = true;
  68. [thisFrame, nextFrame] = [nextFrame, thisFrame];
  69. // Execute this frame
  70. thisFrame.forEach(triggerCallback);
  71. /**
  72. * If we're recording stats then
  73. */
  74. if (stepName && statsBuffer.value) {
  75. statsBuffer.value.frameloop[stepName].push(numCalls);
  76. }
  77. numCalls = 0;
  78. // Clear the frame so no callbacks remain. This is to avoid
  79. // memory leaks should this render step not run for a while.
  80. thisFrame.clear();
  81. isProcessing = false;
  82. if (flushNextFrame) {
  83. flushNextFrame = false;
  84. step.process(frameData);
  85. }
  86. },
  87. };
  88. return step;
  89. }
  90. export { createRenderStep };