@motion-core/motion-gpu 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/advanced.d.ts +1 -0
  2. package/dist/advanced.d.ts.map +1 -0
  3. package/dist/advanced.js +12 -6
  4. package/dist/core/advanced.d.ts +1 -0
  5. package/dist/core/advanced.d.ts.map +1 -0
  6. package/dist/core/advanced.js +12 -5
  7. package/dist/core/current-value.d.ts +1 -0
  8. package/dist/core/current-value.d.ts.map +1 -0
  9. package/dist/core/current-value.js +35 -34
  10. package/dist/core/current-value.js.map +1 -0
  11. package/dist/core/error-diagnostics.d.ts +1 -0
  12. package/dist/core/error-diagnostics.d.ts.map +1 -0
  13. package/dist/core/error-diagnostics.js +70 -137
  14. package/dist/core/error-diagnostics.js.map +1 -0
  15. package/dist/core/error-report.d.ts +1 -0
  16. package/dist/core/error-report.d.ts.map +1 -0
  17. package/dist/core/error-report.js +184 -233
  18. package/dist/core/error-report.js.map +1 -0
  19. package/dist/core/frame-registry.d.ts +1 -0
  20. package/dist/core/frame-registry.d.ts.map +1 -0
  21. package/dist/core/frame-registry.js +546 -662
  22. package/dist/core/frame-registry.js.map +1 -0
  23. package/dist/core/index.d.ts +1 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/index.js +11 -12
  26. package/dist/core/material-preprocess.d.ts +1 -0
  27. package/dist/core/material-preprocess.d.ts.map +1 -0
  28. package/dist/core/material-preprocess.js +128 -151
  29. package/dist/core/material-preprocess.js.map +1 -0
  30. package/dist/core/material.d.ts +1 -0
  31. package/dist/core/material.d.ts.map +1 -0
  32. package/dist/core/material.js +263 -317
  33. package/dist/core/material.js.map +1 -0
  34. package/dist/core/recompile-policy.d.ts +1 -0
  35. package/dist/core/recompile-policy.d.ts.map +1 -0
  36. package/dist/core/recompile-policy.js +18 -13
  37. package/dist/core/recompile-policy.js.map +1 -0
  38. package/dist/core/render-graph.d.ts +1 -0
  39. package/dist/core/render-graph.d.ts.map +1 -0
  40. package/dist/core/render-graph.js +61 -68
  41. package/dist/core/render-graph.js.map +1 -0
  42. package/dist/core/render-targets.d.ts +2 -0
  43. package/dist/core/render-targets.d.ts.map +1 -0
  44. package/dist/core/render-targets.js +52 -53
  45. package/dist/core/render-targets.js.map +1 -0
  46. package/dist/core/renderer.d.ts +1 -0
  47. package/dist/core/renderer.d.ts.map +1 -0
  48. package/dist/core/renderer.js +942 -1081
  49. package/dist/core/renderer.js.map +1 -0
  50. package/dist/core/runtime-loop.d.ts +2 -0
  51. package/dist/core/runtime-loop.d.ts.map +1 -0
  52. package/dist/core/runtime-loop.js +305 -362
  53. package/dist/core/runtime-loop.js.map +1 -0
  54. package/dist/core/scheduler-helpers.d.ts +1 -0
  55. package/dist/core/scheduler-helpers.d.ts.map +1 -0
  56. package/dist/core/scheduler-helpers.js +52 -51
  57. package/dist/core/scheduler-helpers.js.map +1 -0
  58. package/dist/core/shader.d.ts +1 -0
  59. package/dist/core/shader.d.ts.map +1 -0
  60. package/dist/core/shader.js +92 -117
  61. package/dist/core/shader.js.map +1 -0
  62. package/dist/core/texture-loader.d.ts +1 -0
  63. package/dist/core/texture-loader.d.ts.map +1 -0
  64. package/dist/core/texture-loader.js +205 -273
  65. package/dist/core/texture-loader.js.map +1 -0
  66. package/dist/core/textures.d.ts +2 -0
  67. package/dist/core/textures.d.ts.map +1 -0
  68. package/dist/core/textures.js +106 -116
  69. package/dist/core/textures.js.map +1 -0
  70. package/dist/core/types.d.ts +2 -0
  71. package/dist/core/types.d.ts.map +1 -0
  72. package/dist/core/types.js +0 -4
  73. package/dist/core/uniforms.d.ts +1 -0
  74. package/dist/core/uniforms.d.ts.map +1 -0
  75. package/dist/core/uniforms.js +170 -191
  76. package/dist/core/uniforms.js.map +1 -0
  77. package/dist/index.d.ts +1 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +11 -6
  80. package/dist/passes/BlitPass.d.ts +1 -0
  81. package/dist/passes/BlitPass.d.ts.map +1 -0
  82. package/dist/passes/BlitPass.js +23 -18
  83. package/dist/passes/BlitPass.js.map +1 -0
  84. package/dist/passes/CopyPass.d.ts +2 -0
  85. package/dist/passes/CopyPass.d.ts.map +1 -0
  86. package/dist/passes/CopyPass.js +58 -52
  87. package/dist/passes/CopyPass.js.map +1 -0
  88. package/dist/passes/FullscreenPass.d.ts +2 -0
  89. package/dist/passes/FullscreenPass.d.ts.map +1 -0
  90. package/dist/passes/FullscreenPass.js +127 -130
  91. package/dist/passes/FullscreenPass.js.map +1 -0
  92. package/dist/passes/ShaderPass.d.ts +1 -0
  93. package/dist/passes/ShaderPass.d.ts.map +1 -0
  94. package/dist/passes/ShaderPass.js +40 -37
  95. package/dist/passes/ShaderPass.js.map +1 -0
  96. package/dist/passes/index.d.ts +1 -0
  97. package/dist/passes/index.d.ts.map +1 -0
  98. package/dist/passes/index.js +4 -3
  99. package/dist/react/FragCanvas.d.ts +2 -0
  100. package/dist/react/FragCanvas.d.ts.map +1 -0
  101. package/dist/react/FragCanvas.js +234 -211
  102. package/dist/react/FragCanvas.js.map +1 -0
  103. package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
  104. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
  105. package/dist/react/MotionGPUErrorOverlay.js +384 -48
  106. package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
  107. package/dist/react/Portal.d.ts +1 -0
  108. package/dist/react/Portal.d.ts.map +1 -0
  109. package/dist/react/Portal.js +18 -21
  110. package/dist/react/Portal.js.map +1 -0
  111. package/dist/react/advanced.d.ts +1 -0
  112. package/dist/react/advanced.d.ts.map +1 -0
  113. package/dist/react/advanced.js +12 -6
  114. package/dist/react/frame-context.d.ts +1 -0
  115. package/dist/react/frame-context.d.ts.map +1 -0
  116. package/dist/react/frame-context.js +88 -94
  117. package/dist/react/frame-context.js.map +1 -0
  118. package/dist/react/index.d.ts +1 -0
  119. package/dist/react/index.d.ts.map +1 -0
  120. package/dist/react/index.js +10 -9
  121. package/dist/react/motiongpu-context.d.ts +1 -0
  122. package/dist/react/motiongpu-context.d.ts.map +1 -0
  123. package/dist/react/motiongpu-context.js +18 -15
  124. package/dist/react/motiongpu-context.js.map +1 -0
  125. package/dist/react/use-motiongpu-user-context.d.ts +1 -0
  126. package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
  127. package/dist/react/use-motiongpu-user-context.js +83 -82
  128. package/dist/react/use-motiongpu-user-context.js.map +1 -0
  129. package/dist/react/use-texture.d.ts +1 -0
  130. package/dist/react/use-texture.d.ts.map +1 -0
  131. package/dist/react/use-texture.js +132 -152
  132. package/dist/react/use-texture.js.map +1 -0
  133. package/dist/svelte/FragCanvas.svelte.d.ts +2 -0
  134. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
  135. package/dist/svelte/MotionGPUErrorOverlay.svelte +17 -20
  136. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
  137. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
  138. package/dist/svelte/Portal.svelte.d.ts +1 -0
  139. package/dist/svelte/Portal.svelte.d.ts.map +1 -0
  140. package/dist/svelte/advanced.d.ts +1 -0
  141. package/dist/svelte/advanced.d.ts.map +1 -0
  142. package/dist/svelte/advanced.js +11 -6
  143. package/dist/svelte/frame-context.d.ts +1 -0
  144. package/dist/svelte/frame-context.d.ts.map +1 -0
  145. package/dist/svelte/frame-context.js +27 -27
  146. package/dist/svelte/frame-context.js.map +1 -0
  147. package/dist/svelte/index.d.ts +1 -0
  148. package/dist/svelte/index.d.ts.map +1 -0
  149. package/dist/svelte/index.js +10 -9
  150. package/dist/svelte/motiongpu-context.d.ts +1 -0
  151. package/dist/svelte/motiongpu-context.d.ts.map +1 -0
  152. package/dist/svelte/motiongpu-context.js +24 -21
  153. package/dist/svelte/motiongpu-context.js.map +1 -0
  154. package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
  155. package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
  156. package/dist/svelte/use-motiongpu-user-context.js +69 -70
  157. package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
  158. package/dist/svelte/use-texture.d.ts +1 -0
  159. package/dist/svelte/use-texture.d.ts.map +1 -0
  160. package/dist/svelte/use-texture.js +125 -147
  161. package/dist/svelte/use-texture.js.map +1 -0
  162. package/package.json +15 -7
  163. package/src/lib/advanced.ts +6 -0
  164. package/src/lib/core/advanced.ts +12 -0
  165. package/src/lib/core/current-value.ts +64 -0
  166. package/src/lib/core/error-diagnostics.ts +236 -0
  167. package/src/lib/core/error-report.ts +406 -0
  168. package/src/lib/core/frame-registry.ts +1189 -0
  169. package/src/lib/core/index.ts +77 -0
  170. package/src/lib/core/material-preprocess.ts +284 -0
  171. package/src/lib/core/material.ts +667 -0
  172. package/src/lib/core/recompile-policy.ts +31 -0
  173. package/src/lib/core/render-graph.ts +143 -0
  174. package/src/lib/core/render-targets.ts +107 -0
  175. package/src/lib/core/renderer.ts +1547 -0
  176. package/src/lib/core/runtime-loop.ts +458 -0
  177. package/src/lib/core/scheduler-helpers.ts +136 -0
  178. package/src/lib/core/shader.ts +258 -0
  179. package/src/lib/core/texture-loader.ts +476 -0
  180. package/src/lib/core/textures.ts +235 -0
  181. package/src/lib/core/types.ts +582 -0
  182. package/src/lib/core/uniforms.ts +282 -0
  183. package/src/lib/index.ts +6 -0
  184. package/src/lib/passes/BlitPass.ts +54 -0
  185. package/src/lib/passes/CopyPass.ts +80 -0
  186. package/src/lib/passes/FullscreenPass.ts +173 -0
  187. package/src/lib/passes/ShaderPass.ts +88 -0
  188. package/src/lib/passes/index.ts +3 -0
  189. package/src/lib/react/MotionGPUErrorOverlay.tsx +392 -0
  190. package/src/lib/react/advanced.ts +36 -0
  191. package/src/lib/react/frame-context.ts +169 -0
  192. package/src/lib/react/index.ts +51 -0
  193. package/src/lib/react/motiongpu-context.ts +88 -0
  194. package/src/lib/react/use-motiongpu-user-context.ts +186 -0
  195. package/src/lib/react/use-texture.ts +233 -0
  196. package/src/lib/svelte/FragCanvas.svelte +249 -0
  197. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +382 -0
  198. package/src/lib/svelte/Portal.svelte +31 -0
  199. package/src/lib/svelte/advanced.ts +32 -0
  200. package/src/lib/svelte/frame-context.ts +87 -0
  201. package/src/lib/svelte/index.ts +51 -0
  202. package/src/lib/svelte/motiongpu-context.ts +97 -0
  203. package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
  204. package/src/lib/svelte/use-texture.ts +232 -0
  205. package/dist/react/MotionGPUErrorOverlay.tsx +0 -129
  206. /package/{dist → src/lib}/react/FragCanvas.tsx +0 -0
  207. /package/{dist → src/lib}/react/Portal.tsx +0 -0
@@ -1,696 +1,580 @@
1
- import { createCurrentWritable } from './current-value.js';
1
+ import { createCurrentWritable } from "./current-value.js";
2
+ //#region src/lib/core/frame-registry.ts
2
3
  /**
3
- * Default stage key used when task stage is not explicitly specified.
4
- */
5
- const MAIN_STAGE_KEY = Symbol('motiongpu-main-stage');
6
- const RENDER_MODE_INVALIDATION_TOKEN = Symbol('motiongpu-render-mode-change');
4
+ * Default stage key used when task stage is not explicitly specified.
5
+ */
6
+ var MAIN_STAGE_KEY = Symbol("motiongpu-main-stage");
7
+ var RENDER_MODE_INVALIDATION_TOKEN = Symbol("motiongpu-render-mode-change");
7
8
  /**
8
- * Default stage callback that runs tasks immediately.
9
- */
10
- const DEFAULT_STAGE_CALLBACK = (_state, runTasks) => runTasks();
9
+ * Default stage callback that runs tasks immediately.
10
+ */
11
+ var DEFAULT_STAGE_CALLBACK = (_state, runTasks) => runTasks();
11
12
  /**
12
- * Normalizes scalar-or-array options to array form.
13
- */
13
+ * Normalizes scalar-or-array options to array form.
14
+ */
14
15
  function asArray(value) {
15
- if (!value) {
16
- return [];
17
- }
18
- return Array.isArray(value) ? value : [value];
16
+ if (!value) return [];
17
+ return Array.isArray(value) ? value : [value];
19
18
  }
20
19
  /**
21
- * Normalizes frame keys to readable string labels.
22
- */
20
+ * Normalizes frame keys to readable string labels.
21
+ */
23
22
  function frameKeyToString(key) {
24
- return typeof key === 'symbol' ? key.toString() : key;
23
+ return typeof key === "symbol" ? key.toString() : key;
25
24
  }
26
25
  /**
27
- * Extracts task key from either direct key or task reference.
28
- */
26
+ * Extracts task key from either direct key or task reference.
27
+ */
29
28
  function toTaskKey(reference) {
30
- if (typeof reference === 'string' || typeof reference === 'symbol') {
31
- return reference;
32
- }
33
- return reference.key;
29
+ if (typeof reference === "string" || typeof reference === "symbol") return reference;
30
+ return reference.key;
34
31
  }
35
32
  /**
36
- * Extracts stage key from either direct key or stage reference.
37
- */
33
+ * Extracts stage key from either direct key or stage reference.
34
+ */
38
35
  function toStageKey(reference) {
39
- if (typeof reference === 'string' || typeof reference === 'symbol') {
40
- return reference;
41
- }
42
- return reference.key;
36
+ if (typeof reference === "string" || typeof reference === "symbol") return reference;
37
+ return reference.key;
43
38
  }
44
39
  /**
45
- * Resolves invalidation token from static value or resolver callback.
46
- */
40
+ * Resolves invalidation token from static value or resolver callback.
41
+ */
47
42
  function resolveInvalidationToken(token) {
48
- if (token === undefined) {
49
- return null;
50
- }
51
- const resolved = typeof token === 'function' ? token() : token;
52
- if (resolved === null || resolved === undefined) {
53
- return null;
54
- }
55
- return resolved;
43
+ if (token === void 0) return null;
44
+ const resolved = typeof token === "function" ? token() : token;
45
+ if (resolved === null || resolved === void 0) return null;
46
+ return resolved;
56
47
  }
57
48
  /**
58
- * Normalizes task invalidation options to runtime representation.
59
- */
49
+ * Normalizes task invalidation options to runtime representation.
50
+ */
60
51
  function normalizeTaskInvalidation(key, options) {
61
- const explicit = options.invalidation;
62
- if (explicit === undefined) {
63
- if (options.autoInvalidate === false) {
64
- return {
65
- mode: 'never',
66
- lastToken: null,
67
- hasToken: false
68
- };
69
- }
70
- return {
71
- mode: 'always',
72
- token: key,
73
- lastToken: null,
74
- hasToken: false
75
- };
76
- }
77
- if (explicit === 'never' || explicit === 'always') {
78
- if (explicit === 'never') {
79
- return {
80
- mode: explicit,
81
- lastToken: null,
82
- hasToken: false
83
- };
84
- }
85
- return {
86
- mode: explicit,
87
- token: key,
88
- lastToken: null,
89
- hasToken: false
90
- };
91
- }
92
- const mode = explicit.mode ?? 'always';
93
- const token = explicit.token;
94
- if (mode === 'on-change' && token === undefined) {
95
- throw new Error('Task invalidation mode "on-change" requires a token');
96
- }
97
- if (mode === 'never') {
98
- return {
99
- mode,
100
- lastToken: null,
101
- hasToken: false
102
- };
103
- }
104
- if (mode === 'on-change') {
105
- return {
106
- mode,
107
- token: token,
108
- lastToken: null,
109
- hasToken: false
110
- };
111
- }
112
- return {
113
- mode,
114
- token: token ?? key,
115
- lastToken: null,
116
- hasToken: false
117
- };
52
+ const explicit = options.invalidation;
53
+ if (explicit === void 0) {
54
+ if (options.autoInvalidate === false) return {
55
+ mode: "never",
56
+ lastToken: null,
57
+ hasToken: false
58
+ };
59
+ return {
60
+ mode: "always",
61
+ token: key,
62
+ lastToken: null,
63
+ hasToken: false
64
+ };
65
+ }
66
+ if (explicit === "never" || explicit === "always") {
67
+ if (explicit === "never") return {
68
+ mode: explicit,
69
+ lastToken: null,
70
+ hasToken: false
71
+ };
72
+ return {
73
+ mode: explicit,
74
+ token: key,
75
+ lastToken: null,
76
+ hasToken: false
77
+ };
78
+ }
79
+ const mode = explicit.mode ?? "always";
80
+ const token = explicit.token;
81
+ if (mode === "on-change" && token === void 0) throw new Error("Task invalidation mode \"on-change\" requires a token");
82
+ if (mode === "never") return {
83
+ mode,
84
+ lastToken: null,
85
+ hasToken: false
86
+ };
87
+ if (mode === "on-change") return {
88
+ mode,
89
+ token,
90
+ lastToken: null,
91
+ hasToken: false
92
+ };
93
+ return {
94
+ mode,
95
+ token: token ?? key,
96
+ lastToken: null,
97
+ hasToken: false
98
+ };
118
99
  }
119
100
  /**
120
- * Computes aggregate timing stats from sampled durations.
121
- */
101
+ * Computes aggregate timing stats from sampled durations.
102
+ */
122
103
  function buildTimingStats(samples, last) {
123
- if (samples.length === 0) {
124
- return {
125
- last,
126
- avg: 0,
127
- min: 0,
128
- max: 0,
129
- count: 0
130
- };
131
- }
132
- let sum = 0;
133
- let min = Number.POSITIVE_INFINITY;
134
- let max = Number.NEGATIVE_INFINITY;
135
- for (const value of samples) {
136
- sum += value;
137
- if (value < min) {
138
- min = value;
139
- }
140
- if (value > max) {
141
- max = value;
142
- }
143
- }
144
- return {
145
- last,
146
- avg: sum / samples.length,
147
- min,
148
- max,
149
- count: samples.length
150
- };
104
+ if (samples.length === 0) return {
105
+ last,
106
+ avg: 0,
107
+ min: 0,
108
+ max: 0,
109
+ count: 0
110
+ };
111
+ let sum = 0;
112
+ let min = Number.POSITIVE_INFINITY;
113
+ let max = Number.NEGATIVE_INFINITY;
114
+ for (const value of samples) {
115
+ sum += value;
116
+ if (value < min) min = value;
117
+ if (value > max) max = value;
118
+ }
119
+ return {
120
+ last,
121
+ avg: sum / samples.length,
122
+ min,
123
+ max,
124
+ count: samples.length
125
+ };
151
126
  }
152
127
  /**
153
- * Deterministically sorts dependency keys for stable traversal and diagnostics.
154
- */
128
+ * Deterministically sorts dependency keys for stable traversal and diagnostics.
129
+ */
155
130
  function sortDependencyKeys(keys) {
156
- return Array.from(keys).sort((a, b) => frameKeyToString(a).localeCompare(frameKeyToString(b)));
131
+ return Array.from(keys).sort((a, b) => frameKeyToString(a).localeCompare(frameKeyToString(b)));
157
132
  }
158
133
  /**
159
- * Finds one deterministic cycle path in the directed dependency graph.
160
- */
134
+ * Finds one deterministic cycle path in the directed dependency graph.
135
+ */
161
136
  function findDependencyCycle(items, edges) {
162
- const visitState = new Map();
163
- const stack = [];
164
- let cycle = null;
165
- const sortedItems = [...items].sort((a, b) => a.order - b.order);
166
- const visit = (key) => {
167
- visitState.set(key, 1);
168
- stack.push(key);
169
- for (const childKey of sortDependencyKeys(edges.get(key) ?? [])) {
170
- const state = visitState.get(childKey) ?? 0;
171
- if (state === 0) {
172
- if (visit(childKey)) {
173
- return true;
174
- }
175
- continue;
176
- }
177
- if (state === 1) {
178
- const cycleStartIndex = stack.findIndex((entry) => entry === childKey);
179
- const cyclePath = cycleStartIndex === -1 ? [childKey] : stack.slice(cycleStartIndex);
180
- cycle = [...cyclePath, childKey];
181
- return true;
182
- }
183
- }
184
- stack.pop();
185
- visitState.set(key, 2);
186
- return false;
187
- };
188
- for (const item of sortedItems) {
189
- if ((visitState.get(item.key) ?? 0) !== 0) {
190
- continue;
191
- }
192
- if (visit(item.key)) {
193
- return cycle;
194
- }
195
- }
196
- return null;
137
+ const visitState = /* @__PURE__ */ new Map();
138
+ const stack = [];
139
+ let cycle = null;
140
+ const sortedItems = [...items].sort((a, b) => a.order - b.order);
141
+ const visit = (key) => {
142
+ visitState.set(key, 1);
143
+ stack.push(key);
144
+ for (const childKey of sortDependencyKeys(edges.get(key) ?? [])) {
145
+ const state = visitState.get(childKey) ?? 0;
146
+ if (state === 0) {
147
+ if (visit(childKey)) return true;
148
+ continue;
149
+ }
150
+ if (state === 1) {
151
+ const cycleStartIndex = stack.findIndex((entry) => entry === childKey);
152
+ cycle = [...cycleStartIndex === -1 ? [childKey] : stack.slice(cycleStartIndex), childKey];
153
+ return true;
154
+ }
155
+ }
156
+ stack.pop();
157
+ visitState.set(key, 2);
158
+ return false;
159
+ };
160
+ for (const item of sortedItems) {
161
+ if ((visitState.get(item.key) ?? 0) !== 0) continue;
162
+ if (visit(item.key)) return cycle;
163
+ }
164
+ return null;
197
165
  }
198
166
  /**
199
- * Topologically sorts items by `before`/`after` dependencies.
200
- *
201
- * Throws deterministic errors when dependencies are missing or cyclic.
202
- */
167
+ * Topologically sorts items by `before`/`after` dependencies.
168
+ *
169
+ * Throws deterministic errors when dependencies are missing or cyclic.
170
+ */
203
171
  function sortByDependencies(items, getBefore, getAfter, options) {
204
- const itemsByKey = new Map();
205
- for (const item of items) {
206
- itemsByKey.set(item.key, item);
207
- }
208
- const indegree = new Map();
209
- const edges = new Map();
210
- for (const item of items) {
211
- indegree.set(item.key, 0);
212
- edges.set(item.key, new Set());
213
- }
214
- for (const item of items) {
215
- for (const dependencyKey of getAfter(item)) {
216
- if (!itemsByKey.has(dependencyKey)) {
217
- if (options.isKnownExternalDependency?.(dependencyKey)) {
218
- continue;
219
- }
220
- throw new Error(`${options.graphName} dependency error: ${options.getItemLabel(item)} references missing dependency "${frameKeyToString(dependencyKey)}" in "after".`);
221
- }
222
- edges.get(dependencyKey)?.add(item.key);
223
- indegree.set(item.key, (indegree.get(item.key) ?? 0) + 1);
224
- }
225
- for (const dependencyKey of getBefore(item)) {
226
- if (!itemsByKey.has(dependencyKey)) {
227
- if (options.isKnownExternalDependency?.(dependencyKey)) {
228
- continue;
229
- }
230
- throw new Error(`${options.graphName} dependency error: ${options.getItemLabel(item)} references missing dependency "${frameKeyToString(dependencyKey)}" in "before".`);
231
- }
232
- edges.get(item.key)?.add(dependencyKey);
233
- indegree.set(dependencyKey, (indegree.get(dependencyKey) ?? 0) + 1);
234
- }
235
- }
236
- const queue = items.filter((item) => (indegree.get(item.key) ?? 0) === 0);
237
- queue.sort((a, b) => a.order - b.order);
238
- const ordered = [];
239
- while (queue.length > 0) {
240
- const current = queue.shift();
241
- if (!current) {
242
- break;
243
- }
244
- ordered.push(current);
245
- for (const childKey of edges.get(current.key) ?? []) {
246
- const nextDegree = (indegree.get(childKey) ?? 0) - 1;
247
- indegree.set(childKey, nextDegree);
248
- if (nextDegree === 0) {
249
- const child = itemsByKey.get(childKey);
250
- if (child) {
251
- queue.push(child);
252
- queue.sort((a, b) => a.order - b.order);
253
- }
254
- }
255
- }
256
- }
257
- if (ordered.length !== items.length) {
258
- const cycle = findDependencyCycle(items, edges);
259
- if (cycle) {
260
- throw new Error(`${options.graphName} dependency cycle detected: ${cycle.map((key) => frameKeyToString(key)).join(' -> ')}`);
261
- }
262
- throw new Error(`${options.graphName} dependency resolution failed.`);
263
- }
264
- return ordered;
172
+ const itemsByKey = /* @__PURE__ */ new Map();
173
+ for (const item of items) itemsByKey.set(item.key, item);
174
+ const indegree = /* @__PURE__ */ new Map();
175
+ const edges = /* @__PURE__ */ new Map();
176
+ for (const item of items) {
177
+ indegree.set(item.key, 0);
178
+ edges.set(item.key, /* @__PURE__ */ new Set());
179
+ }
180
+ for (const item of items) {
181
+ for (const dependencyKey of getAfter(item)) {
182
+ if (!itemsByKey.has(dependencyKey)) {
183
+ if (options.isKnownExternalDependency?.(dependencyKey)) continue;
184
+ throw new Error(`${options.graphName} dependency error: ${options.getItemLabel(item)} references missing dependency "${frameKeyToString(dependencyKey)}" in "after".`);
185
+ }
186
+ edges.get(dependencyKey)?.add(item.key);
187
+ indegree.set(item.key, (indegree.get(item.key) ?? 0) + 1);
188
+ }
189
+ for (const dependencyKey of getBefore(item)) {
190
+ if (!itemsByKey.has(dependencyKey)) {
191
+ if (options.isKnownExternalDependency?.(dependencyKey)) continue;
192
+ throw new Error(`${options.graphName} dependency error: ${options.getItemLabel(item)} references missing dependency "${frameKeyToString(dependencyKey)}" in "before".`);
193
+ }
194
+ edges.get(item.key)?.add(dependencyKey);
195
+ indegree.set(dependencyKey, (indegree.get(dependencyKey) ?? 0) + 1);
196
+ }
197
+ }
198
+ const queue = items.filter((item) => (indegree.get(item.key) ?? 0) === 0);
199
+ queue.sort((a, b) => a.order - b.order);
200
+ const ordered = [];
201
+ while (queue.length > 0) {
202
+ const current = queue.shift();
203
+ if (!current) break;
204
+ ordered.push(current);
205
+ for (const childKey of edges.get(current.key) ?? []) {
206
+ const nextDegree = (indegree.get(childKey) ?? 0) - 1;
207
+ indegree.set(childKey, nextDegree);
208
+ if (nextDegree === 0) {
209
+ const child = itemsByKey.get(childKey);
210
+ if (child) {
211
+ queue.push(child);
212
+ queue.sort((a, b) => a.order - b.order);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ if (ordered.length !== items.length) {
218
+ const cycle = findDependencyCycle(items, edges);
219
+ if (cycle) throw new Error(`${options.graphName} dependency cycle detected: ${cycle.map((key) => frameKeyToString(key)).join(" -> ")}`);
220
+ throw new Error(`${options.graphName} dependency resolution failed.`);
221
+ }
222
+ return ordered;
265
223
  }
266
224
  /**
267
- * Creates a frame registry used by `FragCanvas` and `useFrame`.
268
- *
269
- * @param options - Initial scheduler options.
270
- * @returns Mutable frame registry instance.
271
- */
272
- export function createFrameRegistry(options) {
273
- let renderMode = options?.renderMode ?? 'always';
274
- let autoRender = options?.autoRender ?? true;
275
- let maxDelta = options?.maxDelta ?? 0.1;
276
- let profilingEnabled = options?.profilingEnabled ?? options?.diagnosticsEnabled ?? false;
277
- let profilingWindow = options?.profilingWindow ?? 120;
278
- let lastRunTimings = null;
279
- const profilingHistory = [];
280
- let hasUntokenizedInvalidation = true;
281
- const invalidationTokens = new Set();
282
- let shouldAdvance = false;
283
- let orderCounter = 0;
284
- const assertMaxDelta = (value) => {
285
- if (!Number.isFinite(value) || value <= 0) {
286
- throw new Error('maxDelta must be a finite number greater than 0');
287
- }
288
- return value;
289
- };
290
- const assertProfilingWindow = (value) => {
291
- if (!Number.isFinite(value) || value <= 0) {
292
- throw new Error('profilingWindow must be a finite number greater than 0');
293
- }
294
- return Math.floor(value);
295
- };
296
- maxDelta = assertMaxDelta(maxDelta);
297
- profilingWindow = assertProfilingWindow(profilingWindow);
298
- const stages = new Map();
299
- let scheduleDirty = true;
300
- let sortedStages = [];
301
- const sortedTasksByStage = new Map();
302
- let scheduleSnapshot = { stages: [] };
303
- const markScheduleDirty = () => {
304
- scheduleDirty = true;
305
- };
306
- const syncSchedule = () => {
307
- if (!scheduleDirty) {
308
- return;
309
- }
310
- const stageList = sortByDependencies(Array.from(stages.values()), (stage) => stage.before, (stage) => stage.after, {
311
- graphName: 'Frame stage graph',
312
- getItemLabel: (stage) => `stage "${frameKeyToString(stage.key)}"`
313
- });
314
- const nextTasksByStage = new Map();
315
- const globalTaskKeys = new Set();
316
- for (const stage of stageList) {
317
- for (const task of stage.tasks.values()) {
318
- globalTaskKeys.add(task.task.key);
319
- }
320
- }
321
- for (const stage of stageList) {
322
- const taskList = sortByDependencies(Array.from(stage.tasks.values()).map((task) => ({
323
- key: task.task.key,
324
- order: task.order,
325
- task
326
- })), (task) => task.task.before, (task) => task.task.after, {
327
- graphName: `Frame task graph for stage "${frameKeyToString(stage.key)}"`,
328
- getItemLabel: (task) => `task "${frameKeyToString(task.key)}"`,
329
- isKnownExternalDependency: (key) => globalTaskKeys.has(key)
330
- }).map((task) => task.task);
331
- nextTasksByStage.set(stage.key, taskList);
332
- }
333
- sortedStages = stageList;
334
- sortedTasksByStage.clear();
335
- for (const [stageKey, taskList] of nextTasksByStage) {
336
- sortedTasksByStage.set(stageKey, taskList);
337
- }
338
- scheduleSnapshot = {
339
- stages: sortedStages.map((stage) => ({
340
- key: frameKeyToString(stage.key),
341
- tasks: (sortedTasksByStage.get(stage.key) ?? []).map((task) => frameKeyToString(task.task.key))
342
- }))
343
- };
344
- scheduleDirty = false;
345
- };
346
- const pushProfile = (timings) => {
347
- profilingHistory.push(timings);
348
- while (profilingHistory.length > profilingWindow) {
349
- profilingHistory.shift();
350
- }
351
- };
352
- const clearProfiling = () => {
353
- profilingHistory.length = 0;
354
- lastRunTimings = null;
355
- };
356
- const buildProfilingSnapshot = () => {
357
- if (!profilingEnabled) {
358
- return null;
359
- }
360
- const stageBuckets = new Map();
361
- const totalDurations = [];
362
- for (const frame of profilingHistory) {
363
- totalDurations.push(frame.total);
364
- for (const [stageKey, stageTiming] of Object.entries(frame.stages)) {
365
- const stageBucket = stageBuckets.get(stageKey) ?? {
366
- durations: [],
367
- taskDurations: new Map()
368
- };
369
- stageBucket.durations.push(stageTiming.duration);
370
- for (const [taskKey, taskDuration] of Object.entries(stageTiming.tasks)) {
371
- const bucket = stageBucket.taskDurations.get(taskKey) ?? [];
372
- bucket.push(taskDuration);
373
- stageBucket.taskDurations.set(taskKey, bucket);
374
- }
375
- stageBuckets.set(stageKey, stageBucket);
376
- }
377
- }
378
- const stagesSnapshot = {};
379
- for (const [stageKey, stageBucket] of stageBuckets) {
380
- const lastStageDuration = lastRunTimings?.stages[stageKey]?.duration ?? 0;
381
- const taskSnapshot = {};
382
- for (const [taskKey, taskDurations] of stageBucket.taskDurations) {
383
- const lastTaskDuration = lastRunTimings?.stages[stageKey]?.tasks[taskKey] ?? 0;
384
- taskSnapshot[taskKey] = buildTimingStats(taskDurations, lastTaskDuration);
385
- }
386
- stagesSnapshot[stageKey] = {
387
- timings: buildTimingStats(stageBucket.durations, lastStageDuration),
388
- tasks: taskSnapshot
389
- };
390
- }
391
- return {
392
- window: profilingWindow,
393
- frameCount: profilingHistory.length,
394
- lastFrame: lastRunTimings,
395
- total: buildTimingStats(totalDurations, lastRunTimings?.total ?? 0),
396
- stages: stagesSnapshot
397
- };
398
- };
399
- const ensureStage = (stageReference, stageOptions) => {
400
- const stageKey = toStageKey(stageReference);
401
- const existing = stages.get(stageKey);
402
- if (existing) {
403
- if (stageOptions?.before !== undefined) {
404
- existing.before = new Set(stageOptions.before.map((entry) => toStageKey(entry)));
405
- markScheduleDirty();
406
- }
407
- if (stageOptions?.after !== undefined) {
408
- existing.after = new Set(stageOptions.after.map((entry) => toStageKey(entry)));
409
- markScheduleDirty();
410
- }
411
- if (stageOptions && Object.prototype.hasOwnProperty.call(stageOptions, 'callback')) {
412
- existing.callback = stageOptions.callback ?? DEFAULT_STAGE_CALLBACK;
413
- }
414
- return existing;
415
- }
416
- const stage = {
417
- key: stageKey,
418
- order: orderCounter++,
419
- started: true,
420
- before: new Set((stageOptions?.before ?? []).map((entry) => toStageKey(entry))),
421
- after: new Set((stageOptions?.after ?? []).map((entry) => toStageKey(entry))),
422
- callback: stageOptions?.callback ?? DEFAULT_STAGE_CALLBACK,
423
- tasks: new Map()
424
- };
425
- stages.set(stageKey, stage);
426
- markScheduleDirty();
427
- return stage;
428
- };
429
- ensureStage(MAIN_STAGE_KEY);
430
- const resolveEffectiveRunning = (task) => {
431
- const running = task.started && (task.running?.() ?? true);
432
- if (task.lastRunning !== running) {
433
- task.lastRunning = running;
434
- task.startedStoreSet(running);
435
- }
436
- return running;
437
- };
438
- const hasPendingInvalidation = () => {
439
- return hasUntokenizedInvalidation || invalidationTokens.size > 0;
440
- };
441
- const invalidateWithToken = (token) => {
442
- if (token === undefined) {
443
- hasUntokenizedInvalidation = true;
444
- return;
445
- }
446
- invalidationTokens.add(token);
447
- };
448
- const applyTaskInvalidation = (task) => {
449
- const config = task.invalidation;
450
- if (config.mode === 'never') {
451
- return;
452
- }
453
- if (config.mode === 'always') {
454
- const token = resolveInvalidationToken(config.token);
455
- invalidateWithToken(token ?? task.task.key);
456
- return;
457
- }
458
- const token = resolveInvalidationToken(config.token);
459
- if (token === null) {
460
- config.hasToken = false;
461
- config.lastToken = null;
462
- return;
463
- }
464
- const changed = !config.hasToken || config.lastToken !== token;
465
- config.hasToken = true;
466
- config.lastToken = token;
467
- if (changed) {
468
- invalidateWithToken(token);
469
- }
470
- };
471
- return {
472
- register(keyOrCallback, callbackOrOptions, maybeOptions) {
473
- const key = typeof keyOrCallback === 'function'
474
- ? Symbol('motiongpu-task')
475
- : keyOrCallback;
476
- const callback = typeof keyOrCallback === 'function' ? keyOrCallback : callbackOrOptions;
477
- const taskOptions = typeof keyOrCallback === 'function'
478
- ? (callbackOrOptions ?? {})
479
- : (maybeOptions ?? {});
480
- if (typeof callback !== 'function') {
481
- throw new Error('useFrame requires a callback');
482
- }
483
- const before = asArray(taskOptions.before);
484
- const after = asArray(taskOptions.after);
485
- const inferredStage = [...before, ...after].find((entry) => typeof entry === 'object' && entry !== null && 'stage' in entry);
486
- const stageKey = taskOptions.stage
487
- ? toStageKey(taskOptions.stage)
488
- : (inferredStage?.stage ?? MAIN_STAGE_KEY);
489
- const stage = ensureStage(stageKey);
490
- const startedWritable = createCurrentWritable(taskOptions.autoStart ?? true);
491
- const internalTask = {
492
- task: { key, stage: stage.key },
493
- callback,
494
- order: orderCounter++,
495
- started: taskOptions.autoStart ?? true,
496
- lastRunning: taskOptions.autoStart ?? true,
497
- startedStoreSet: startedWritable.set,
498
- startedStore: { subscribe: startedWritable.subscribe },
499
- before: new Set(before.map((entry) => toTaskKey(entry))),
500
- after: new Set(after.map((entry) => toTaskKey(entry))),
501
- invalidation: normalizeTaskInvalidation(key, taskOptions)
502
- };
503
- if (taskOptions.running) {
504
- internalTask.running = taskOptions.running;
505
- }
506
- stage.tasks.set(key, internalTask);
507
- markScheduleDirty();
508
- internalTask.startedStoreSet(resolveEffectiveRunning(internalTask));
509
- const start = () => {
510
- internalTask.started = true;
511
- resolveEffectiveRunning(internalTask);
512
- };
513
- const stop = () => {
514
- internalTask.started = false;
515
- resolveEffectiveRunning(internalTask);
516
- };
517
- return {
518
- task: internalTask.task,
519
- start,
520
- stop,
521
- started: internalTask.startedStore,
522
- unsubscribe: () => {
523
- if (stage.tasks.delete(key)) {
524
- markScheduleDirty();
525
- }
526
- }
527
- };
528
- },
529
- run(state) {
530
- const clampedDelta = Math.min(state.delta, maxDelta);
531
- const frameState = clampedDelta === state.delta
532
- ? state
533
- : {
534
- ...state,
535
- delta: clampedDelta
536
- };
537
- syncSchedule();
538
- const frameStart = profilingEnabled ? performance.now() : 0;
539
- const stageTimings = {};
540
- for (const stage of sortedStages) {
541
- if (!stage.started) {
542
- continue;
543
- }
544
- const stageStart = profilingEnabled ? performance.now() : 0;
545
- const taskTimings = {};
546
- const taskList = sortedTasksByStage.get(stage.key) ?? [];
547
- stage.callback(frameState, () => {
548
- for (const task of taskList) {
549
- if (!resolveEffectiveRunning(task)) {
550
- continue;
551
- }
552
- const taskStart = profilingEnabled ? performance.now() : 0;
553
- task.callback(frameState);
554
- if (profilingEnabled) {
555
- taskTimings[frameKeyToString(task.task.key)] = performance.now() - taskStart;
556
- }
557
- applyTaskInvalidation(task);
558
- }
559
- });
560
- if (profilingEnabled) {
561
- stageTimings[frameKeyToString(stage.key)] = {
562
- duration: performance.now() - stageStart,
563
- tasks: taskTimings
564
- };
565
- }
566
- }
567
- if (profilingEnabled) {
568
- const timings = {
569
- total: performance.now() - frameStart,
570
- stages: stageTimings
571
- };
572
- lastRunTimings = timings;
573
- pushProfile(timings);
574
- }
575
- },
576
- invalidate(token) {
577
- invalidateWithToken(token);
578
- },
579
- advance() {
580
- shouldAdvance = true;
581
- invalidateWithToken();
582
- },
583
- shouldRender() {
584
- if (!autoRender) {
585
- return false;
586
- }
587
- if (renderMode === 'always') {
588
- return true;
589
- }
590
- if (renderMode === 'on-demand') {
591
- return shouldAdvance || hasPendingInvalidation();
592
- }
593
- return shouldAdvance;
594
- },
595
- endFrame() {
596
- hasUntokenizedInvalidation = false;
597
- invalidationTokens.clear();
598
- shouldAdvance = false;
599
- },
600
- setRenderMode(mode) {
601
- if (renderMode === mode) {
602
- return;
603
- }
604
- renderMode = mode;
605
- shouldAdvance = false;
606
- if (mode === 'on-demand') {
607
- invalidateWithToken(RENDER_MODE_INVALIDATION_TOKEN);
608
- }
609
- },
610
- setAutoRender(enabled) {
611
- autoRender = enabled;
612
- },
613
- setMaxDelta(value) {
614
- maxDelta = assertMaxDelta(value);
615
- },
616
- setProfilingEnabled(enabled) {
617
- profilingEnabled = enabled;
618
- if (!enabled) {
619
- clearProfiling();
620
- }
621
- },
622
- setProfilingWindow(window) {
623
- profilingWindow = assertProfilingWindow(window);
624
- while (profilingHistory.length > profilingWindow) {
625
- profilingHistory.shift();
626
- }
627
- },
628
- resetProfiling() {
629
- clearProfiling();
630
- },
631
- setDiagnosticsEnabled(enabled) {
632
- profilingEnabled = enabled;
633
- if (!enabled) {
634
- clearProfiling();
635
- }
636
- },
637
- getRenderMode() {
638
- return renderMode;
639
- },
640
- getAutoRender() {
641
- return autoRender;
642
- },
643
- getMaxDelta() {
644
- return maxDelta;
645
- },
646
- getProfilingEnabled() {
647
- return profilingEnabled;
648
- },
649
- getProfilingWindow() {
650
- return profilingWindow;
651
- },
652
- getProfilingSnapshot() {
653
- return buildProfilingSnapshot();
654
- },
655
- getDiagnosticsEnabled() {
656
- return profilingEnabled;
657
- },
658
- getLastRunTimings() {
659
- return lastRunTimings;
660
- },
661
- getSchedule() {
662
- syncSchedule();
663
- return scheduleSnapshot;
664
- },
665
- createStage(key, options) {
666
- const stageOptions = options
667
- ? {
668
- ...(Object.prototype.hasOwnProperty.call(options, 'before')
669
- ? { before: asArray(options.before) }
670
- : {}),
671
- ...(Object.prototype.hasOwnProperty.call(options, 'after')
672
- ? { after: asArray(options.after) }
673
- : {}),
674
- ...(Object.prototype.hasOwnProperty.call(options, 'callback')
675
- ? { callback: options.callback ?? null }
676
- : {})
677
- }
678
- : undefined;
679
- const stage = ensureStage(key, stageOptions);
680
- return { key: stage.key };
681
- },
682
- getStage(key) {
683
- const stage = stages.get(key);
684
- if (!stage) {
685
- return undefined;
686
- }
687
- return { key: stage.key };
688
- },
689
- clear() {
690
- for (const stage of stages.values()) {
691
- stage.tasks.clear();
692
- }
693
- markScheduleDirty();
694
- }
695
- };
225
+ * Creates a frame registry used by `FragCanvas` and `useFrame`.
226
+ *
227
+ * @param options - Initial scheduler options.
228
+ * @returns Mutable frame registry instance.
229
+ */
230
+ function createFrameRegistry(options) {
231
+ let renderMode = options?.renderMode ?? "always";
232
+ let autoRender = options?.autoRender ?? true;
233
+ let maxDelta = options?.maxDelta ?? .1;
234
+ let profilingEnabled = options?.profilingEnabled ?? options?.diagnosticsEnabled ?? false;
235
+ let profilingWindow = options?.profilingWindow ?? 120;
236
+ let lastRunTimings = null;
237
+ const profilingHistory = [];
238
+ let hasUntokenizedInvalidation = true;
239
+ const invalidationTokens = /* @__PURE__ */ new Set();
240
+ let shouldAdvance = false;
241
+ let orderCounter = 0;
242
+ const assertMaxDelta = (value) => {
243
+ if (!Number.isFinite(value) || value <= 0) throw new Error("maxDelta must be a finite number greater than 0");
244
+ return value;
245
+ };
246
+ const assertProfilingWindow = (value) => {
247
+ if (!Number.isFinite(value) || value <= 0) throw new Error("profilingWindow must be a finite number greater than 0");
248
+ return Math.floor(value);
249
+ };
250
+ maxDelta = assertMaxDelta(maxDelta);
251
+ profilingWindow = assertProfilingWindow(profilingWindow);
252
+ const stages = /* @__PURE__ */ new Map();
253
+ let scheduleDirty = true;
254
+ let sortedStages = [];
255
+ const sortedTasksByStage = /* @__PURE__ */ new Map();
256
+ let scheduleSnapshot = { stages: [] };
257
+ const markScheduleDirty = () => {
258
+ scheduleDirty = true;
259
+ };
260
+ const syncSchedule = () => {
261
+ if (!scheduleDirty) return;
262
+ const stageList = sortByDependencies(Array.from(stages.values()), (stage) => stage.before, (stage) => stage.after, {
263
+ graphName: "Frame stage graph",
264
+ getItemLabel: (stage) => `stage "${frameKeyToString(stage.key)}"`
265
+ });
266
+ const nextTasksByStage = /* @__PURE__ */ new Map();
267
+ const globalTaskKeys = /* @__PURE__ */ new Set();
268
+ for (const stage of stageList) for (const task of stage.tasks.values()) globalTaskKeys.add(task.task.key);
269
+ for (const stage of stageList) {
270
+ const taskList = sortByDependencies(Array.from(stage.tasks.values()).map((task) => ({
271
+ key: task.task.key,
272
+ order: task.order,
273
+ task
274
+ })), (task) => task.task.before, (task) => task.task.after, {
275
+ graphName: `Frame task graph for stage "${frameKeyToString(stage.key)}"`,
276
+ getItemLabel: (task) => `task "${frameKeyToString(task.key)}"`,
277
+ isKnownExternalDependency: (key) => globalTaskKeys.has(key)
278
+ }).map((task) => task.task);
279
+ nextTasksByStage.set(stage.key, taskList);
280
+ }
281
+ sortedStages = stageList;
282
+ sortedTasksByStage.clear();
283
+ for (const [stageKey, taskList] of nextTasksByStage) sortedTasksByStage.set(stageKey, taskList);
284
+ scheduleSnapshot = { stages: sortedStages.map((stage) => ({
285
+ key: frameKeyToString(stage.key),
286
+ tasks: (sortedTasksByStage.get(stage.key) ?? []).map((task) => frameKeyToString(task.task.key))
287
+ })) };
288
+ scheduleDirty = false;
289
+ };
290
+ const pushProfile = (timings) => {
291
+ profilingHistory.push(timings);
292
+ while (profilingHistory.length > profilingWindow) profilingHistory.shift();
293
+ };
294
+ const clearProfiling = () => {
295
+ profilingHistory.length = 0;
296
+ lastRunTimings = null;
297
+ };
298
+ const buildProfilingSnapshot = () => {
299
+ if (!profilingEnabled) return null;
300
+ const stageBuckets = /* @__PURE__ */ new Map();
301
+ const totalDurations = [];
302
+ for (const frame of profilingHistory) {
303
+ totalDurations.push(frame.total);
304
+ for (const [stageKey, stageTiming] of Object.entries(frame.stages)) {
305
+ const stageBucket = stageBuckets.get(stageKey) ?? {
306
+ durations: [],
307
+ taskDurations: /* @__PURE__ */ new Map()
308
+ };
309
+ stageBucket.durations.push(stageTiming.duration);
310
+ for (const [taskKey, taskDuration] of Object.entries(stageTiming.tasks)) {
311
+ const bucket = stageBucket.taskDurations.get(taskKey) ?? [];
312
+ bucket.push(taskDuration);
313
+ stageBucket.taskDurations.set(taskKey, bucket);
314
+ }
315
+ stageBuckets.set(stageKey, stageBucket);
316
+ }
317
+ }
318
+ const stagesSnapshot = {};
319
+ for (const [stageKey, stageBucket] of stageBuckets) {
320
+ const lastStageDuration = lastRunTimings?.stages[stageKey]?.duration ?? 0;
321
+ const taskSnapshot = {};
322
+ for (const [taskKey, taskDurations] of stageBucket.taskDurations) taskSnapshot[taskKey] = buildTimingStats(taskDurations, lastRunTimings?.stages[stageKey]?.tasks[taskKey] ?? 0);
323
+ stagesSnapshot[stageKey] = {
324
+ timings: buildTimingStats(stageBucket.durations, lastStageDuration),
325
+ tasks: taskSnapshot
326
+ };
327
+ }
328
+ return {
329
+ window: profilingWindow,
330
+ frameCount: profilingHistory.length,
331
+ lastFrame: lastRunTimings,
332
+ total: buildTimingStats(totalDurations, lastRunTimings?.total ?? 0),
333
+ stages: stagesSnapshot
334
+ };
335
+ };
336
+ const ensureStage = (stageReference, stageOptions) => {
337
+ const stageKey = toStageKey(stageReference);
338
+ const existing = stages.get(stageKey);
339
+ if (existing) {
340
+ if (stageOptions?.before !== void 0) {
341
+ existing.before = new Set(stageOptions.before.map((entry) => toStageKey(entry)));
342
+ markScheduleDirty();
343
+ }
344
+ if (stageOptions?.after !== void 0) {
345
+ existing.after = new Set(stageOptions.after.map((entry) => toStageKey(entry)));
346
+ markScheduleDirty();
347
+ }
348
+ if (stageOptions && Object.prototype.hasOwnProperty.call(stageOptions, "callback")) existing.callback = stageOptions.callback ?? DEFAULT_STAGE_CALLBACK;
349
+ return existing;
350
+ }
351
+ const stage = {
352
+ key: stageKey,
353
+ order: orderCounter++,
354
+ started: true,
355
+ before: new Set((stageOptions?.before ?? []).map((entry) => toStageKey(entry))),
356
+ after: new Set((stageOptions?.after ?? []).map((entry) => toStageKey(entry))),
357
+ callback: stageOptions?.callback ?? DEFAULT_STAGE_CALLBACK,
358
+ tasks: /* @__PURE__ */ new Map()
359
+ };
360
+ stages.set(stageKey, stage);
361
+ markScheduleDirty();
362
+ return stage;
363
+ };
364
+ ensureStage(MAIN_STAGE_KEY);
365
+ const resolveEffectiveRunning = (task) => {
366
+ const running = task.started && (task.running?.() ?? true);
367
+ if (task.lastRunning !== running) {
368
+ task.lastRunning = running;
369
+ task.startedStoreSet(running);
370
+ }
371
+ return running;
372
+ };
373
+ const hasPendingInvalidation = () => {
374
+ return hasUntokenizedInvalidation || invalidationTokens.size > 0;
375
+ };
376
+ const invalidateWithToken = (token) => {
377
+ if (token === void 0) {
378
+ hasUntokenizedInvalidation = true;
379
+ return;
380
+ }
381
+ invalidationTokens.add(token);
382
+ };
383
+ const applyTaskInvalidation = (task) => {
384
+ const config = task.invalidation;
385
+ if (config.mode === "never") return;
386
+ if (config.mode === "always") {
387
+ invalidateWithToken(resolveInvalidationToken(config.token) ?? task.task.key);
388
+ return;
389
+ }
390
+ const token = resolveInvalidationToken(config.token);
391
+ if (token === null) {
392
+ config.hasToken = false;
393
+ config.lastToken = null;
394
+ return;
395
+ }
396
+ const changed = !config.hasToken || config.lastToken !== token;
397
+ config.hasToken = true;
398
+ config.lastToken = token;
399
+ if (changed) invalidateWithToken(token);
400
+ };
401
+ return {
402
+ register(keyOrCallback, callbackOrOptions, maybeOptions) {
403
+ const key = typeof keyOrCallback === "function" ? Symbol("motiongpu-task") : keyOrCallback;
404
+ const callback = typeof keyOrCallback === "function" ? keyOrCallback : callbackOrOptions;
405
+ const taskOptions = typeof keyOrCallback === "function" ? callbackOrOptions ?? {} : maybeOptions ?? {};
406
+ if (typeof callback !== "function") throw new Error("useFrame requires a callback");
407
+ const before = asArray(taskOptions.before);
408
+ const after = asArray(taskOptions.after);
409
+ const inferredStage = [...before, ...after].find((entry) => typeof entry === "object" && entry !== null && "stage" in entry);
410
+ const stage = ensureStage(taskOptions.stage ? toStageKey(taskOptions.stage) : inferredStage?.stage ?? MAIN_STAGE_KEY);
411
+ const startedWritable = createCurrentWritable(taskOptions.autoStart ?? true);
412
+ const internalTask = {
413
+ task: {
414
+ key,
415
+ stage: stage.key
416
+ },
417
+ callback,
418
+ order: orderCounter++,
419
+ started: taskOptions.autoStart ?? true,
420
+ lastRunning: taskOptions.autoStart ?? true,
421
+ startedStoreSet: startedWritable.set,
422
+ startedStore: { subscribe: startedWritable.subscribe },
423
+ before: new Set(before.map((entry) => toTaskKey(entry))),
424
+ after: new Set(after.map((entry) => toTaskKey(entry))),
425
+ invalidation: normalizeTaskInvalidation(key, taskOptions)
426
+ };
427
+ if (taskOptions.running) internalTask.running = taskOptions.running;
428
+ stage.tasks.set(key, internalTask);
429
+ markScheduleDirty();
430
+ internalTask.startedStoreSet(resolveEffectiveRunning(internalTask));
431
+ const start = () => {
432
+ internalTask.started = true;
433
+ resolveEffectiveRunning(internalTask);
434
+ };
435
+ const stop = () => {
436
+ internalTask.started = false;
437
+ resolveEffectiveRunning(internalTask);
438
+ };
439
+ return {
440
+ task: internalTask.task,
441
+ start,
442
+ stop,
443
+ started: internalTask.startedStore,
444
+ unsubscribe: () => {
445
+ if (stage.tasks.delete(key)) markScheduleDirty();
446
+ }
447
+ };
448
+ },
449
+ run(state) {
450
+ const clampedDelta = Math.min(state.delta, maxDelta);
451
+ const frameState = clampedDelta === state.delta ? state : {
452
+ ...state,
453
+ delta: clampedDelta
454
+ };
455
+ syncSchedule();
456
+ const frameStart = profilingEnabled ? performance.now() : 0;
457
+ const stageTimings = {};
458
+ for (const stage of sortedStages) {
459
+ if (!stage.started) continue;
460
+ const stageStart = profilingEnabled ? performance.now() : 0;
461
+ const taskTimings = {};
462
+ const taskList = sortedTasksByStage.get(stage.key) ?? [];
463
+ stage.callback(frameState, () => {
464
+ for (const task of taskList) {
465
+ if (!resolveEffectiveRunning(task)) continue;
466
+ const taskStart = profilingEnabled ? performance.now() : 0;
467
+ task.callback(frameState);
468
+ if (profilingEnabled) taskTimings[frameKeyToString(task.task.key)] = performance.now() - taskStart;
469
+ applyTaskInvalidation(task);
470
+ }
471
+ });
472
+ if (profilingEnabled) stageTimings[frameKeyToString(stage.key)] = {
473
+ duration: performance.now() - stageStart,
474
+ tasks: taskTimings
475
+ };
476
+ }
477
+ if (profilingEnabled) {
478
+ const timings = {
479
+ total: performance.now() - frameStart,
480
+ stages: stageTimings
481
+ };
482
+ lastRunTimings = timings;
483
+ pushProfile(timings);
484
+ }
485
+ },
486
+ invalidate(token) {
487
+ invalidateWithToken(token);
488
+ },
489
+ advance() {
490
+ shouldAdvance = true;
491
+ invalidateWithToken();
492
+ },
493
+ shouldRender() {
494
+ if (!autoRender) return false;
495
+ if (renderMode === "always") return true;
496
+ if (renderMode === "on-demand") return shouldAdvance || hasPendingInvalidation();
497
+ return shouldAdvance;
498
+ },
499
+ endFrame() {
500
+ hasUntokenizedInvalidation = false;
501
+ invalidationTokens.clear();
502
+ shouldAdvance = false;
503
+ },
504
+ setRenderMode(mode) {
505
+ if (renderMode === mode) return;
506
+ renderMode = mode;
507
+ shouldAdvance = false;
508
+ if (mode === "on-demand") invalidateWithToken(RENDER_MODE_INVALIDATION_TOKEN);
509
+ },
510
+ setAutoRender(enabled) {
511
+ autoRender = enabled;
512
+ },
513
+ setMaxDelta(value) {
514
+ maxDelta = assertMaxDelta(value);
515
+ },
516
+ setProfilingEnabled(enabled) {
517
+ profilingEnabled = enabled;
518
+ if (!enabled) clearProfiling();
519
+ },
520
+ setProfilingWindow(window) {
521
+ profilingWindow = assertProfilingWindow(window);
522
+ while (profilingHistory.length > profilingWindow) profilingHistory.shift();
523
+ },
524
+ resetProfiling() {
525
+ clearProfiling();
526
+ },
527
+ setDiagnosticsEnabled(enabled) {
528
+ profilingEnabled = enabled;
529
+ if (!enabled) clearProfiling();
530
+ },
531
+ getRenderMode() {
532
+ return renderMode;
533
+ },
534
+ getAutoRender() {
535
+ return autoRender;
536
+ },
537
+ getMaxDelta() {
538
+ return maxDelta;
539
+ },
540
+ getProfilingEnabled() {
541
+ return profilingEnabled;
542
+ },
543
+ getProfilingWindow() {
544
+ return profilingWindow;
545
+ },
546
+ getProfilingSnapshot() {
547
+ return buildProfilingSnapshot();
548
+ },
549
+ getDiagnosticsEnabled() {
550
+ return profilingEnabled;
551
+ },
552
+ getLastRunTimings() {
553
+ return lastRunTimings;
554
+ },
555
+ getSchedule() {
556
+ syncSchedule();
557
+ return scheduleSnapshot;
558
+ },
559
+ createStage(key, options) {
560
+ return { key: ensureStage(key, options ? {
561
+ ...Object.prototype.hasOwnProperty.call(options, "before") ? { before: asArray(options.before) } : {},
562
+ ...Object.prototype.hasOwnProperty.call(options, "after") ? { after: asArray(options.after) } : {},
563
+ ...Object.prototype.hasOwnProperty.call(options, "callback") ? { callback: options.callback ?? null } : {}
564
+ } : void 0).key };
565
+ },
566
+ getStage(key) {
567
+ const stage = stages.get(key);
568
+ if (!stage) return;
569
+ return { key: stage.key };
570
+ },
571
+ clear() {
572
+ for (const stage of stages.values()) stage.tasks.clear();
573
+ markScheduleDirty();
574
+ }
575
+ };
696
576
  }
577
+ //#endregion
578
+ export { createFrameRegistry };
579
+
580
+ //# sourceMappingURL=frame-registry.js.map