@react-three/fiber 10.0.0-alpha.1 → 10.0.0-canary.164c7be
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.
- package/LICENSE +21 -21
- package/dist/index.cjs +1256 -1419
- package/dist/index.d.cts +1887 -1699
- package/dist/index.d.mts +1887 -1699
- package/dist/index.d.ts +1887 -1699
- package/dist/index.mjs +1216 -1398
- package/dist/legacy.cjs +1222 -1410
- package/dist/legacy.d.cts +1888 -1700
- package/dist/legacy.d.mts +1888 -1700
- package/dist/legacy.d.ts +1888 -1700
- package/dist/legacy.mjs +1182 -1389
- package/dist/webgpu/index.cjs +1937 -1470
- package/dist/webgpu/index.d.cts +2145 -1722
- package/dist/webgpu/index.d.mts +2145 -1722
- package/dist/webgpu/index.d.ts +2145 -1722
- package/dist/webgpu/index.mjs +1890 -1449
- package/package.json +8 -5
- package/react-reconciler/constants.js +1 -9
- package/react-reconciler/index.js +4 -20
- package/readme.md +244 -318
package/dist/index.cjs
CHANGED
|
@@ -7,10 +7,18 @@ const jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
const React = require('react');
|
|
8
8
|
const useMeasure = require('react-use-measure');
|
|
9
9
|
const itsFine = require('its-fine');
|
|
10
|
+
const fiber = require('@react-three/fiber');
|
|
11
|
+
const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
|
|
12
|
+
const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
|
|
13
|
+
const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
|
|
14
|
+
const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
|
|
15
|
+
const gainmapJs = require('@monogrid/gainmap-js');
|
|
10
16
|
const Tb = require('scheduler');
|
|
11
17
|
const traditional = require('zustand/traditional');
|
|
18
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
12
19
|
const suspendReact = require('suspend-react');
|
|
13
20
|
|
|
21
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
22
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
15
23
|
|
|
16
24
|
function _interopNamespaceCompat(e) {
|
|
@@ -56,9 +64,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
56
64
|
WebGLRenderer: three.WebGLRenderer
|
|
57
65
|
}, [webgpu__namespace]);
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
68
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
69
|
+
function registerPrimary(id, renderer, store) {
|
|
70
|
+
if (primaryRegistry.has(id)) {
|
|
71
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
72
|
+
}
|
|
73
|
+
const entry = { renderer, store };
|
|
74
|
+
primaryRegistry.set(id, entry);
|
|
75
|
+
const subscribers = pendingSubscribers.get(id);
|
|
76
|
+
if (subscribers) {
|
|
77
|
+
subscribers.forEach((callback) => callback(entry));
|
|
78
|
+
pendingSubscribers.delete(id);
|
|
79
|
+
}
|
|
80
|
+
return () => {
|
|
81
|
+
const currentEntry = primaryRegistry.get(id);
|
|
82
|
+
if (currentEntry?.renderer === renderer) {
|
|
83
|
+
primaryRegistry.delete(id);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getPrimary(id) {
|
|
88
|
+
return primaryRegistry.get(id);
|
|
89
|
+
}
|
|
90
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
91
|
+
const existing = primaryRegistry.get(id);
|
|
92
|
+
if (existing) {
|
|
93
|
+
return Promise.resolve(existing);
|
|
94
|
+
}
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const timeoutId = setTimeout(() => {
|
|
97
|
+
const subscribers = pendingSubscribers.get(id);
|
|
98
|
+
if (subscribers) {
|
|
99
|
+
const index = subscribers.indexOf(callback);
|
|
100
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
101
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
102
|
+
}
|
|
103
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
104
|
+
}, timeout);
|
|
105
|
+
const callback = (entry) => {
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
resolve(entry);
|
|
108
|
+
};
|
|
109
|
+
if (!pendingSubscribers.has(id)) {
|
|
110
|
+
pendingSubscribers.set(id, []);
|
|
111
|
+
}
|
|
112
|
+
pendingSubscribers.get(id).push(callback);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function hasPrimary(id) {
|
|
116
|
+
return primaryRegistry.has(id);
|
|
117
|
+
}
|
|
118
|
+
function unregisterPrimary(id) {
|
|
119
|
+
primaryRegistry.delete(id);
|
|
120
|
+
}
|
|
121
|
+
function getPrimaryIds() {
|
|
122
|
+
return Array.from(primaryRegistry.keys());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const presetsObj = {
|
|
126
|
+
apartment: "lebombo_1k.hdr",
|
|
127
|
+
city: "potsdamer_platz_1k.hdr",
|
|
128
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
129
|
+
forest: "forest_slope_1k.hdr",
|
|
130
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
131
|
+
night: "dikhololo_night_1k.hdr",
|
|
132
|
+
park: "rooitou_park_1k.hdr",
|
|
133
|
+
studio: "studio_small_03_1k.hdr",
|
|
134
|
+
sunset: "venice_sunset_1k.hdr",
|
|
135
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
139
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
140
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
141
|
+
function useEnvironment({
|
|
142
|
+
files = defaultFiles,
|
|
143
|
+
path = "",
|
|
144
|
+
preset = void 0,
|
|
145
|
+
colorSpace = void 0,
|
|
146
|
+
extensions
|
|
147
|
+
} = {}) {
|
|
148
|
+
if (preset) {
|
|
149
|
+
validatePreset(preset);
|
|
150
|
+
files = presetsObj[preset];
|
|
151
|
+
path = CUBEMAP_ROOT;
|
|
152
|
+
}
|
|
153
|
+
const multiFile = isArray(files);
|
|
154
|
+
const { extension, isCubemap } = getExtension(files);
|
|
155
|
+
const loader = getLoader$1(extension);
|
|
156
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
157
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
158
|
+
React.useLayoutEffect(() => {
|
|
159
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
160
|
+
function clearGainmapTexture() {
|
|
161
|
+
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
162
|
+
}
|
|
163
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
164
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
165
|
+
const loaderResult = fiber.useLoader(
|
|
166
|
+
loader,
|
|
167
|
+
multiFile ? [files] : files,
|
|
168
|
+
(loader2) => {
|
|
169
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
170
|
+
loader2.setRenderer?.(renderer);
|
|
171
|
+
}
|
|
172
|
+
loader2.setPath?.(path);
|
|
173
|
+
if (extensions) extensions(loader2);
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
let texture = multiFile ? (
|
|
177
|
+
// @ts-ignore
|
|
178
|
+
loaderResult[0]
|
|
179
|
+
) : loaderResult;
|
|
180
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
181
|
+
texture = texture.renderTarget?.texture;
|
|
182
|
+
}
|
|
183
|
+
texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
|
|
184
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
185
|
+
return texture;
|
|
186
|
+
}
|
|
187
|
+
const preloadDefaultOptions = {
|
|
188
|
+
files: defaultFiles,
|
|
189
|
+
path: "",
|
|
190
|
+
preset: void 0,
|
|
191
|
+
extensions: void 0
|
|
192
|
+
};
|
|
193
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
194
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
195
|
+
let { files, path = "" } = options;
|
|
196
|
+
const { preset, extensions } = options;
|
|
197
|
+
if (preset) {
|
|
198
|
+
validatePreset(preset);
|
|
199
|
+
files = presetsObj[preset];
|
|
200
|
+
path = CUBEMAP_ROOT;
|
|
201
|
+
}
|
|
202
|
+
const { extension } = getExtension(files);
|
|
203
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
204
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
205
|
+
}
|
|
206
|
+
const loader = getLoader$1(extension);
|
|
207
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
208
|
+
fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
209
|
+
loader2.setPath?.(path);
|
|
210
|
+
if (extensions) extensions(loader2);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
const clearDefaultOptins = {
|
|
214
|
+
files: defaultFiles,
|
|
215
|
+
preset: void 0
|
|
216
|
+
};
|
|
217
|
+
useEnvironment.clear = (clearOptions) => {
|
|
218
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
219
|
+
let { files } = options;
|
|
220
|
+
const { preset } = options;
|
|
221
|
+
if (preset) {
|
|
222
|
+
validatePreset(preset);
|
|
223
|
+
files = presetsObj[preset];
|
|
224
|
+
}
|
|
225
|
+
const { extension } = getExtension(files);
|
|
226
|
+
const loader = getLoader$1(extension);
|
|
227
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
228
|
+
fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
|
|
229
|
+
};
|
|
230
|
+
function validatePreset(preset) {
|
|
231
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
232
|
+
}
|
|
233
|
+
function getExtension(files) {
|
|
234
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
235
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
236
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
237
|
+
const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
|
|
238
|
+
return { extension, isCubemap, isGainmap };
|
|
239
|
+
}
|
|
240
|
+
function getLoader$1(extension) {
|
|
241
|
+
const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
|
|
242
|
+
return loader;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
246
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
247
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
248
|
+
sceneProps = {
|
|
249
|
+
backgroundBlurriness: 0,
|
|
250
|
+
backgroundIntensity: 1,
|
|
251
|
+
backgroundRotation: [0, 0, 0],
|
|
252
|
+
environmentIntensity: 1,
|
|
253
|
+
environmentRotation: [0, 0, 0],
|
|
254
|
+
...sceneProps
|
|
255
|
+
};
|
|
256
|
+
const target = resolveScene(scene || defaultScene);
|
|
257
|
+
const oldbg = target.background;
|
|
258
|
+
const oldenv = target.environment;
|
|
259
|
+
const oldSceneProps = {
|
|
260
|
+
// @ts-ignore
|
|
261
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
262
|
+
// @ts-ignore
|
|
263
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
environmentIntensity: target.environmentIntensity,
|
|
268
|
+
// @ts-ignore
|
|
269
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
270
|
+
};
|
|
271
|
+
if (background !== "only") target.environment = texture;
|
|
272
|
+
if (background) target.background = texture;
|
|
273
|
+
fiber.applyProps(target, sceneProps);
|
|
274
|
+
return () => {
|
|
275
|
+
if (background !== "only") target.environment = oldenv;
|
|
276
|
+
if (background) target.background = oldbg;
|
|
277
|
+
fiber.applyProps(target, oldSceneProps);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
281
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
282
|
+
React__namespace.useLayoutEffect(() => {
|
|
283
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function EnvironmentCube({
|
|
288
|
+
background = false,
|
|
289
|
+
scene,
|
|
290
|
+
blur,
|
|
291
|
+
backgroundBlurriness,
|
|
292
|
+
backgroundIntensity,
|
|
293
|
+
backgroundRotation,
|
|
294
|
+
environmentIntensity,
|
|
295
|
+
environmentRotation,
|
|
296
|
+
...rest
|
|
297
|
+
}) {
|
|
298
|
+
const texture = useEnvironment(rest);
|
|
299
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
300
|
+
React__namespace.useLayoutEffect(() => {
|
|
301
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
302
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
303
|
+
backgroundIntensity,
|
|
304
|
+
backgroundRotation,
|
|
305
|
+
environmentIntensity,
|
|
306
|
+
environmentRotation
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
React__namespace.useEffect(() => {
|
|
310
|
+
return () => {
|
|
311
|
+
texture.dispose();
|
|
312
|
+
};
|
|
313
|
+
}, [texture]);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function EnvironmentPortal({
|
|
317
|
+
children,
|
|
318
|
+
near = 0.1,
|
|
319
|
+
far = 1e3,
|
|
320
|
+
resolution = 256,
|
|
321
|
+
frames = 1,
|
|
322
|
+
map,
|
|
323
|
+
background = false,
|
|
324
|
+
blur,
|
|
325
|
+
backgroundBlurriness,
|
|
326
|
+
backgroundIntensity,
|
|
327
|
+
backgroundRotation,
|
|
328
|
+
environmentIntensity,
|
|
329
|
+
environmentRotation,
|
|
330
|
+
scene,
|
|
331
|
+
files,
|
|
332
|
+
path,
|
|
333
|
+
preset = void 0,
|
|
334
|
+
extensions
|
|
335
|
+
}) {
|
|
336
|
+
const gl = fiber.useThree((state) => state.gl);
|
|
337
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
338
|
+
const camera = React__namespace.useRef(null);
|
|
339
|
+
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
340
|
+
const fbo = React__namespace.useMemo(() => {
|
|
341
|
+
const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
|
|
342
|
+
fbo2.texture.type = webgpu.HalfFloatType;
|
|
343
|
+
return fbo2;
|
|
344
|
+
}, [resolution]);
|
|
345
|
+
React__namespace.useEffect(() => {
|
|
346
|
+
return () => {
|
|
347
|
+
fbo.dispose();
|
|
348
|
+
};
|
|
349
|
+
}, [fbo]);
|
|
350
|
+
React__namespace.useLayoutEffect(() => {
|
|
351
|
+
if (frames === 1) {
|
|
352
|
+
const autoClear = gl.autoClear;
|
|
353
|
+
gl.autoClear = true;
|
|
354
|
+
camera.current.update(gl, virtualScene);
|
|
355
|
+
gl.autoClear = autoClear;
|
|
356
|
+
}
|
|
357
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
358
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
359
|
+
backgroundIntensity,
|
|
360
|
+
backgroundRotation,
|
|
361
|
+
environmentIntensity,
|
|
362
|
+
environmentRotation
|
|
363
|
+
});
|
|
364
|
+
}, [
|
|
365
|
+
children,
|
|
366
|
+
virtualScene,
|
|
367
|
+
fbo.texture,
|
|
368
|
+
scene,
|
|
369
|
+
defaultScene,
|
|
370
|
+
background,
|
|
371
|
+
frames,
|
|
372
|
+
gl,
|
|
373
|
+
blur,
|
|
374
|
+
backgroundBlurriness,
|
|
375
|
+
backgroundIntensity,
|
|
376
|
+
backgroundRotation,
|
|
377
|
+
environmentIntensity,
|
|
378
|
+
environmentRotation
|
|
379
|
+
]);
|
|
380
|
+
let count = 1;
|
|
381
|
+
fiber.useFrame(() => {
|
|
382
|
+
if (frames === Infinity || count < frames) {
|
|
383
|
+
const autoClear = gl.autoClear;
|
|
384
|
+
gl.autoClear = true;
|
|
385
|
+
camera.current.update(gl, virtualScene);
|
|
386
|
+
gl.autoClear = autoClear;
|
|
387
|
+
count++;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
392
|
+
children,
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
394
|
+
files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
395
|
+
] }),
|
|
396
|
+
virtualScene
|
|
397
|
+
) });
|
|
398
|
+
}
|
|
399
|
+
function EnvironmentGround(props) {
|
|
400
|
+
const textureDefault = useEnvironment(props);
|
|
401
|
+
const texture = props.map || textureDefault;
|
|
402
|
+
React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
|
|
403
|
+
React__namespace.useEffect(() => {
|
|
404
|
+
return () => {
|
|
405
|
+
textureDefault.dispose();
|
|
406
|
+
};
|
|
407
|
+
}, [textureDefault]);
|
|
408
|
+
const height = props.ground?.height ?? 15;
|
|
409
|
+
const radius = props.ground?.radius ?? 60;
|
|
410
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
411
|
+
const args = React__namespace.useMemo(
|
|
412
|
+
() => [texture, height, radius],
|
|
413
|
+
[texture, height, radius]
|
|
414
|
+
);
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
|
|
418
|
+
] });
|
|
419
|
+
}
|
|
420
|
+
function EnvironmentColor({ color, scene }) {
|
|
421
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
422
|
+
React__namespace.useLayoutEffect(() => {
|
|
423
|
+
if (color === void 0) return;
|
|
424
|
+
const target = resolveScene(scene || defaultScene);
|
|
425
|
+
const oldBg = target.background;
|
|
426
|
+
target.background = new webgpu.Color(color);
|
|
427
|
+
return () => {
|
|
428
|
+
target.background = oldBg;
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function EnvironmentDualSource(props) {
|
|
434
|
+
const { backgroundFiles, ...envProps } = props;
|
|
435
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
function Environment(props) {
|
|
441
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
442
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
|
|
443
|
+
}
|
|
444
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
|
|
446
|
+
}
|
|
447
|
+
return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var __defProp = Object.defineProperty;
|
|
451
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
452
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
62
453
|
const act = React__namespace["act"];
|
|
63
454
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
64
455
|
function useMutableCallback(fn) {
|
|
@@ -90,7 +481,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
90
481
|
return _a = class extends React__namespace.Component {
|
|
91
482
|
constructor() {
|
|
92
483
|
super(...arguments);
|
|
93
|
-
__publicField
|
|
484
|
+
__publicField(this, "state", { error: false });
|
|
94
485
|
}
|
|
95
486
|
componentDidCatch(err) {
|
|
96
487
|
this.props.set(err);
|
|
@@ -98,7 +489,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
98
489
|
render() {
|
|
99
490
|
return this.state.error ? null : this.props.children;
|
|
100
491
|
}
|
|
101
|
-
}, __publicField
|
|
492
|
+
}, __publicField(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
102
493
|
})();
|
|
103
494
|
|
|
104
495
|
const is = {
|
|
@@ -235,7 +626,8 @@ function prepare(target, root, type, props) {
|
|
|
235
626
|
object,
|
|
236
627
|
eventCount: 0,
|
|
237
628
|
handlers: {},
|
|
238
|
-
isHidden: false
|
|
629
|
+
isHidden: false,
|
|
630
|
+
deferredRefs: []
|
|
239
631
|
};
|
|
240
632
|
if (object) object.__r3f = instance;
|
|
241
633
|
}
|
|
@@ -284,7 +676,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
284
676
|
let occlusionSetupPromise = null;
|
|
285
677
|
function enableOcclusion(store) {
|
|
286
678
|
const state = store.getState();
|
|
287
|
-
const { internal, renderer
|
|
679
|
+
const { internal, renderer } = state;
|
|
288
680
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
289
681
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
290
682
|
if (!hasOcclusionSupport) {
|
|
@@ -447,6 +839,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
447
839
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
448
840
|
}
|
|
449
841
|
|
|
842
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
843
|
+
function fromRef(ref) {
|
|
844
|
+
return { [FROM_REF]: ref };
|
|
845
|
+
}
|
|
846
|
+
function isFromRef(value) {
|
|
847
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
851
|
+
function once(...args) {
|
|
852
|
+
return { [ONCE]: args.length ? args : true };
|
|
853
|
+
}
|
|
854
|
+
function isOnce(value) {
|
|
855
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
856
|
+
}
|
|
857
|
+
|
|
450
858
|
const RESERVED_PROPS = [
|
|
451
859
|
"children",
|
|
452
860
|
"key",
|
|
@@ -517,7 +925,7 @@ function getMemoizedPrototype(root) {
|
|
|
517
925
|
ctor = new root.constructor();
|
|
518
926
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
519
927
|
}
|
|
520
|
-
} catch
|
|
928
|
+
} catch {
|
|
521
929
|
}
|
|
522
930
|
return ctor;
|
|
523
931
|
}
|
|
@@ -548,7 +956,7 @@ function applyProps(object, props) {
|
|
|
548
956
|
const rootState = instance && findInitialRoot(instance).getState();
|
|
549
957
|
const prevHandlers = instance?.eventCount;
|
|
550
958
|
for (const prop in props) {
|
|
551
|
-
|
|
959
|
+
const value = props[prop];
|
|
552
960
|
if (RESERVED_PROPS.includes(prop)) continue;
|
|
553
961
|
if (instance && EVENT_REGEX.test(prop)) {
|
|
554
962
|
if (typeof value === "function") instance.handlers[prop] = value;
|
|
@@ -563,6 +971,25 @@ function applyProps(object, props) {
|
|
|
563
971
|
continue;
|
|
564
972
|
}
|
|
565
973
|
if (value === void 0) continue;
|
|
974
|
+
if (isFromRef(value)) {
|
|
975
|
+
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (isOnce(value)) {
|
|
979
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
980
|
+
if (instance) {
|
|
981
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
982
|
+
instance.appliedOnce.add(prop);
|
|
983
|
+
}
|
|
984
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
985
|
+
const args = value[ONCE];
|
|
986
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
987
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
988
|
+
} else if (args !== true && args.length > 0) {
|
|
989
|
+
targetRoot[targetKey] = args[0];
|
|
990
|
+
}
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
566
993
|
let { root, key, target } = resolve(object, prop);
|
|
567
994
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
568
995
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -585,7 +1012,10 @@ function applyProps(object, props) {
|
|
|
585
1012
|
else target.set(value);
|
|
586
1013
|
} else {
|
|
587
1014
|
root[key] = value;
|
|
588
|
-
if (
|
|
1015
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1016
|
+
root.needsUpdate = true;
|
|
1017
|
+
}
|
|
1018
|
+
if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
589
1019
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
590
1020
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
591
1021
|
}
|
|
@@ -618,38 +1048,60 @@ function applyProps(object, props) {
|
|
|
618
1048
|
return object;
|
|
619
1049
|
}
|
|
620
1050
|
|
|
1051
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1052
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1053
|
+
function getPointerState(internal, pointerId) {
|
|
1054
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1055
|
+
if (!state) {
|
|
1056
|
+
state = {
|
|
1057
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1058
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1059
|
+
initialClick: [0, 0],
|
|
1060
|
+
initialHits: []
|
|
1061
|
+
};
|
|
1062
|
+
internal.pointerMap.set(pointerId, state);
|
|
1063
|
+
}
|
|
1064
|
+
return state;
|
|
1065
|
+
}
|
|
1066
|
+
function getPointerId(event) {
|
|
1067
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1068
|
+
}
|
|
621
1069
|
function makeId(event) {
|
|
622
1070
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
623
1071
|
}
|
|
624
|
-
function releaseInternalPointerCapture(
|
|
625
|
-
const
|
|
1072
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1073
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1074
|
+
if (!pointerState) return;
|
|
1075
|
+
const captureData = pointerState.captured.get(obj);
|
|
626
1076
|
if (captureData) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
capturedMap.delete(pointerId);
|
|
630
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
631
|
-
}
|
|
1077
|
+
pointerState.captured.delete(obj);
|
|
1078
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
632
1079
|
}
|
|
633
1080
|
}
|
|
634
1081
|
function removeInteractivity(store, object) {
|
|
635
1082
|
const { internal } = store.getState();
|
|
636
1083
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1084
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1085
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1086
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1087
|
+
if (value.eventObject === object || value.object === object) {
|
|
1088
|
+
pointerState.hovered.delete(key);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
if (pointerState.captured.has(object)) {
|
|
1092
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
641
1093
|
}
|
|
642
|
-
}
|
|
643
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
644
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
645
|
-
});
|
|
1094
|
+
}
|
|
646
1095
|
unregisterVisibility(store, object);
|
|
647
1096
|
}
|
|
648
1097
|
function createEvents(store) {
|
|
649
|
-
function calculateDistance(event) {
|
|
1098
|
+
function calculateDistance(event, pointerId) {
|
|
650
1099
|
const { internal } = store.getState();
|
|
651
|
-
const
|
|
652
|
-
|
|
1100
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1101
|
+
if (!pointerState) return 0;
|
|
1102
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1103
|
+
const dx = event.offsetX - initialX;
|
|
1104
|
+
const dy = event.offsetY - initialY;
|
|
653
1105
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
654
1106
|
}
|
|
655
1107
|
function filterPointerEvents(objects) {
|
|
@@ -685,6 +1137,15 @@ function createEvents(store) {
|
|
|
685
1137
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
686
1138
|
}
|
|
687
1139
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1140
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1141
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1142
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1143
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1144
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1145
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1146
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
688
1149
|
const aState = getRootState(a.object);
|
|
689
1150
|
const bState = getRootState(b.object);
|
|
690
1151
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -700,14 +1161,19 @@ function createEvents(store) {
|
|
|
700
1161
|
for (const hit of hits) {
|
|
701
1162
|
let eventObject = hit.object;
|
|
702
1163
|
while (eventObject) {
|
|
703
|
-
if (eventObject.__r3f?.eventCount)
|
|
1164
|
+
if (eventObject.__r3f?.eventCount) {
|
|
704
1165
|
intersections.push({ ...hit, eventObject });
|
|
1166
|
+
}
|
|
705
1167
|
eventObject = eventObject.parent;
|
|
706
1168
|
}
|
|
707
1169
|
}
|
|
708
|
-
if ("pointerId" in event
|
|
709
|
-
|
|
710
|
-
|
|
1170
|
+
if ("pointerId" in event) {
|
|
1171
|
+
const pointerId = event.pointerId;
|
|
1172
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1173
|
+
if (pointerState?.captured.size) {
|
|
1174
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1175
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1176
|
+
}
|
|
711
1177
|
}
|
|
712
1178
|
}
|
|
713
1179
|
return intersections;
|
|
@@ -720,28 +1186,26 @@ function createEvents(store) {
|
|
|
720
1186
|
if (state) {
|
|
721
1187
|
const { raycaster, pointer, camera, internal } = state;
|
|
722
1188
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
723
|
-
const hasPointerCapture = (id) =>
|
|
1189
|
+
const hasPointerCapture = (id) => {
|
|
1190
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1191
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1192
|
+
};
|
|
724
1193
|
const setPointerCapture = (id) => {
|
|
725
1194
|
const captureData = { intersection: hit, target: event.target };
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
} else {
|
|
729
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
730
|
-
}
|
|
1195
|
+
const pointerState = getPointerState(internal, id);
|
|
1196
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
731
1197
|
event.target.setPointerCapture(id);
|
|
732
1198
|
};
|
|
733
1199
|
const releasePointerCapture = (id) => {
|
|
734
|
-
|
|
735
|
-
if (captures) {
|
|
736
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
737
|
-
}
|
|
1200
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
738
1201
|
};
|
|
739
|
-
|
|
740
|
-
for (
|
|
741
|
-
|
|
1202
|
+
const extractEventProps = {};
|
|
1203
|
+
for (const prop in event) {
|
|
1204
|
+
const property = event[prop];
|
|
742
1205
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
743
1206
|
}
|
|
744
|
-
|
|
1207
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1208
|
+
const raycastEvent = {
|
|
745
1209
|
...hit,
|
|
746
1210
|
...extractEventProps,
|
|
747
1211
|
pointer,
|
|
@@ -751,18 +1215,19 @@ function createEvents(store) {
|
|
|
751
1215
|
unprojectedPoint,
|
|
752
1216
|
ray: raycaster.ray,
|
|
753
1217
|
camera,
|
|
1218
|
+
pointerId: eventPointerId,
|
|
754
1219
|
// Hijack stopPropagation, which just sets a flag
|
|
755
1220
|
stopPropagation() {
|
|
756
|
-
const
|
|
1221
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
757
1222
|
if (
|
|
758
1223
|
// ...if this pointer hasn't been captured
|
|
759
|
-
!
|
|
760
|
-
|
|
1224
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1225
|
+
pointerState.captured.has(hit.eventObject)
|
|
761
1226
|
) {
|
|
762
1227
|
raycastEvent.stopped = localState.stopped = true;
|
|
763
|
-
if (
|
|
1228
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
764
1229
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
765
|
-
cancelPointer([...higher, hit]);
|
|
1230
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
766
1231
|
}
|
|
767
1232
|
}
|
|
768
1233
|
},
|
|
@@ -778,15 +1243,18 @@ function createEvents(store) {
|
|
|
778
1243
|
}
|
|
779
1244
|
return intersections;
|
|
780
1245
|
}
|
|
781
|
-
function cancelPointer(intersections) {
|
|
1246
|
+
function cancelPointer(intersections, pointerId) {
|
|
782
1247
|
const { internal } = store.getState();
|
|
783
|
-
|
|
1248
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1249
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1250
|
+
if (!pointerState) return;
|
|
1251
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
784
1252
|
if (!intersections.length || !intersections.find(
|
|
785
1253
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
786
1254
|
)) {
|
|
787
1255
|
const eventObject = hoveredObj.eventObject;
|
|
788
1256
|
const instance = eventObject.__r3f;
|
|
789
|
-
|
|
1257
|
+
pointerState.hovered.delete(hoveredId);
|
|
790
1258
|
if (instance?.eventCount) {
|
|
791
1259
|
const handlers = instance.handlers;
|
|
792
1260
|
const data = { ...hoveredObj, intersections };
|
|
@@ -815,41 +1283,118 @@ function createEvents(store) {
|
|
|
815
1283
|
instance?.handlers.onDropMissed?.(event);
|
|
816
1284
|
}
|
|
817
1285
|
}
|
|
1286
|
+
function cleanupPointer(pointerId) {
|
|
1287
|
+
const { internal } = store.getState();
|
|
1288
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1289
|
+
if (pointerState) {
|
|
1290
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1291
|
+
const eventObject = hoveredObj.eventObject;
|
|
1292
|
+
const instance = eventObject.__r3f;
|
|
1293
|
+
if (instance?.eventCount) {
|
|
1294
|
+
const handlers = instance.handlers;
|
|
1295
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1296
|
+
handlers.onPointerOut?.(data);
|
|
1297
|
+
handlers.onPointerLeave?.(data);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
internal.pointerMap.delete(pointerId);
|
|
1301
|
+
}
|
|
1302
|
+
internal.pointerDirty.delete(pointerId);
|
|
1303
|
+
}
|
|
1304
|
+
function processDeferredPointer(event, pointerId) {
|
|
1305
|
+
const state = store.getState();
|
|
1306
|
+
const { internal } = state;
|
|
1307
|
+
if (!state.events.enabled) return;
|
|
1308
|
+
const filter = filterPointerEvents;
|
|
1309
|
+
const hits = intersect(event, filter);
|
|
1310
|
+
cancelPointer(hits, pointerId);
|
|
1311
|
+
function onIntersect(data) {
|
|
1312
|
+
const eventObject = data.eventObject;
|
|
1313
|
+
const instance = eventObject.__r3f;
|
|
1314
|
+
if (!instance?.eventCount) return;
|
|
1315
|
+
const handlers = instance.handlers;
|
|
1316
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1317
|
+
const id = makeId(data);
|
|
1318
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1319
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1320
|
+
if (!hoveredItem) {
|
|
1321
|
+
pointerState.hovered.set(id, data);
|
|
1322
|
+
handlers.onPointerOver?.(data);
|
|
1323
|
+
handlers.onPointerEnter?.(data);
|
|
1324
|
+
} else if (hoveredItem.stopped) {
|
|
1325
|
+
data.stopPropagation();
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
handlers.onPointerMove?.(data);
|
|
1329
|
+
}
|
|
1330
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1331
|
+
}
|
|
818
1332
|
function handlePointer(name) {
|
|
819
1333
|
switch (name) {
|
|
820
1334
|
case "onPointerLeave":
|
|
821
|
-
case "onPointerCancel":
|
|
822
1335
|
case "onDragLeave":
|
|
823
1336
|
return () => cancelPointer([]);
|
|
1337
|
+
// Global cancel of these events
|
|
1338
|
+
case "onPointerCancel":
|
|
1339
|
+
return (event) => {
|
|
1340
|
+
const pointerId = getPointerId(event);
|
|
1341
|
+
cleanupPointer(pointerId);
|
|
1342
|
+
};
|
|
824
1343
|
case "onLostPointerCapture":
|
|
825
1344
|
return (event) => {
|
|
826
1345
|
const { internal } = store.getState();
|
|
827
|
-
|
|
1346
|
+
const pointerId = getPointerId(event);
|
|
1347
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1348
|
+
if (pointerState?.captured.size) {
|
|
828
1349
|
requestAnimationFrame(() => {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1350
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1351
|
+
if (pointerState2?.captured.size) {
|
|
1352
|
+
pointerState2.captured.clear();
|
|
832
1353
|
}
|
|
1354
|
+
cancelPointer([], pointerId);
|
|
833
1355
|
});
|
|
834
1356
|
}
|
|
835
1357
|
};
|
|
836
1358
|
}
|
|
837
1359
|
return function handleEvent(event) {
|
|
838
1360
|
const state = store.getState();
|
|
839
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1361
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1362
|
+
const pointerId = getPointerId(event);
|
|
840
1363
|
internal.lastEvent.current = event;
|
|
841
|
-
if (!
|
|
1364
|
+
if (!events.enabled) return;
|
|
842
1365
|
const isPointerMove = name === "onPointerMove";
|
|
843
1366
|
const isDragOver = name === "onDragOver";
|
|
844
1367
|
const isDrop = name === "onDrop";
|
|
845
1368
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1369
|
+
const isPointerDown = name === "onPointerDown";
|
|
1370
|
+
const isPointerUp = name === "onPointerUp";
|
|
1371
|
+
const isWheel = name === "onWheel";
|
|
1372
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1373
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1374
|
+
events.compute?.(event, state);
|
|
1375
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1379
|
+
events.compute?.(event, state);
|
|
1380
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1384
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1385
|
+
internal.pointerDirty.delete(pointerId);
|
|
1386
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1387
|
+
}
|
|
846
1388
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
847
1389
|
const hits = intersect(event, filter);
|
|
848
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
849
|
-
if (
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1390
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1391
|
+
if (isPointerDown) {
|
|
1392
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1393
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1394
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1395
|
+
}
|
|
1396
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1397
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
853
1398
|
if (isClickEvent && !hits.length) {
|
|
854
1399
|
if (delta <= 2) {
|
|
855
1400
|
pointerMissed(event, internal.interaction);
|
|
@@ -864,7 +1409,9 @@ function createEvents(store) {
|
|
|
864
1409
|
dropMissed(event, internal.interaction);
|
|
865
1410
|
if (onDropMissed) onDropMissed(event);
|
|
866
1411
|
}
|
|
867
|
-
if (isPointerMove || isDragOver)
|
|
1412
|
+
if (isPointerMove || isDragOver) {
|
|
1413
|
+
cancelPointer(hits, pointerId);
|
|
1414
|
+
}
|
|
868
1415
|
function onIntersect(data) {
|
|
869
1416
|
const eventObject = data.eventObject;
|
|
870
1417
|
const instance = eventObject.__r3f;
|
|
@@ -873,9 +1420,10 @@ function createEvents(store) {
|
|
|
873
1420
|
if (isPointerMove) {
|
|
874
1421
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
875
1422
|
const id = makeId(data);
|
|
876
|
-
const
|
|
1423
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1424
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
877
1425
|
if (!hoveredItem) {
|
|
878
|
-
|
|
1426
|
+
pointerState2.hovered.set(id, data);
|
|
879
1427
|
handlers.onPointerOver?.(data);
|
|
880
1428
|
handlers.onPointerEnter?.(data);
|
|
881
1429
|
} else if (hoveredItem.stopped) {
|
|
@@ -885,9 +1433,10 @@ function createEvents(store) {
|
|
|
885
1433
|
handlers.onPointerMove?.(data);
|
|
886
1434
|
} else if (isDragOver) {
|
|
887
1435
|
const id = makeId(data);
|
|
888
|
-
const
|
|
1436
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1437
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
889
1438
|
if (!hoveredItem) {
|
|
890
|
-
|
|
1439
|
+
pointerState2.hovered.set(id, data);
|
|
891
1440
|
handlers.onDragOverEnter?.(data);
|
|
892
1441
|
} else if (hoveredItem.stopped) {
|
|
893
1442
|
data.stopPropagation();
|
|
@@ -898,18 +1447,18 @@ function createEvents(store) {
|
|
|
898
1447
|
} else {
|
|
899
1448
|
const handler = handlers[name];
|
|
900
1449
|
if (handler) {
|
|
901
|
-
if (!isClickEvent ||
|
|
1450
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
902
1451
|
pointerMissed(
|
|
903
1452
|
event,
|
|
904
|
-
internal.interaction.filter((object) => !
|
|
1453
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
905
1454
|
);
|
|
906
1455
|
handler(data);
|
|
907
1456
|
}
|
|
908
1457
|
} else {
|
|
909
|
-
if (isClickEvent &&
|
|
1458
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
910
1459
|
pointerMissed(
|
|
911
1460
|
event,
|
|
912
|
-
internal.interaction.filter((object) => !
|
|
1461
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
913
1462
|
);
|
|
914
1463
|
}
|
|
915
1464
|
}
|
|
@@ -918,7 +1467,15 @@ function createEvents(store) {
|
|
|
918
1467
|
handleIntersects(hits, event, delta, onIntersect);
|
|
919
1468
|
};
|
|
920
1469
|
}
|
|
921
|
-
|
|
1470
|
+
function flushDeferredPointers() {
|
|
1471
|
+
const { internal, events } = store.getState();
|
|
1472
|
+
if (!events.frameTimedRaycasts) return;
|
|
1473
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1474
|
+
processDeferredPointer(event, pointerId);
|
|
1475
|
+
}
|
|
1476
|
+
internal.pointerDirty.clear();
|
|
1477
|
+
}
|
|
1478
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
922
1479
|
}
|
|
923
1480
|
const DOM_EVENTS = {
|
|
924
1481
|
onClick: ["click", false],
|
|
@@ -937,11 +1494,16 @@ const DOM_EVENTS = {
|
|
|
937
1494
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
938
1495
|
};
|
|
939
1496
|
function createPointerEvents(store) {
|
|
940
|
-
const { handlePointer } = createEvents(store);
|
|
1497
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1498
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1499
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
941
1500
|
return {
|
|
942
1501
|
priority: 1,
|
|
943
1502
|
enabled: true,
|
|
944
|
-
|
|
1503
|
+
frameTimedRaycasts: true,
|
|
1504
|
+
alwaysFireOnScroll: true,
|
|
1505
|
+
updateOnFrame: false,
|
|
1506
|
+
compute(event, state) {
|
|
945
1507
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
946
1508
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
947
1509
|
},
|
|
@@ -950,11 +1512,33 @@ function createPointerEvents(store) {
|
|
|
950
1512
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
951
1513
|
{}
|
|
952
1514
|
),
|
|
953
|
-
update: () => {
|
|
1515
|
+
update: (pointerId) => {
|
|
1516
|
+
const { events, internal } = store.getState();
|
|
1517
|
+
if (!events.handlers) return;
|
|
1518
|
+
if (pointerId !== void 0) {
|
|
1519
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1520
|
+
if (event) {
|
|
1521
|
+
internal.pointerDirty.delete(pointerId);
|
|
1522
|
+
processDeferredPointer(event, pointerId);
|
|
1523
|
+
} else if (internal.lastEvent?.current) {
|
|
1524
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1525
|
+
}
|
|
1526
|
+
} else {
|
|
1527
|
+
flushDeferredPointers();
|
|
1528
|
+
if (internal.lastEvent?.current) {
|
|
1529
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
flush: () => {
|
|
954
1534
|
const { events, internal } = store.getState();
|
|
955
|
-
|
|
1535
|
+
flushDeferredPointers();
|
|
1536
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1537
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1538
|
+
}
|
|
956
1539
|
},
|
|
957
1540
|
connect: (target) => {
|
|
1541
|
+
if (!target) return;
|
|
958
1542
|
const { set, events } = store.getState();
|
|
959
1543
|
events.disconnect?.();
|
|
960
1544
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -978,6 +1562,32 @@ function createPointerEvents(store) {
|
|
|
978
1562
|
}
|
|
979
1563
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
980
1564
|
}
|
|
1565
|
+
},
|
|
1566
|
+
registerPointer: (config) => {
|
|
1567
|
+
const pointerId = nextXRPointerId++;
|
|
1568
|
+
xrPointers.set(pointerId, config);
|
|
1569
|
+
const { internal } = store.getState();
|
|
1570
|
+
getPointerState(internal, pointerId);
|
|
1571
|
+
return pointerId;
|
|
1572
|
+
},
|
|
1573
|
+
unregisterPointer: (pointerId) => {
|
|
1574
|
+
xrPointers.delete(pointerId);
|
|
1575
|
+
const { internal } = store.getState();
|
|
1576
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1577
|
+
if (pointerState) {
|
|
1578
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1579
|
+
const eventObject = hoveredObj.eventObject;
|
|
1580
|
+
const instance = eventObject.__r3f;
|
|
1581
|
+
if (instance?.eventCount) {
|
|
1582
|
+
const handlers = instance.handlers;
|
|
1583
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1584
|
+
handlers.onPointerOut?.(data);
|
|
1585
|
+
handlers.onPointerLeave?.(data);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
internal.pointerMap.delete(pointerId);
|
|
1589
|
+
}
|
|
1590
|
+
internal.pointerDirty.delete(pointerId);
|
|
981
1591
|
}
|
|
982
1592
|
};
|
|
983
1593
|
}
|
|
@@ -1068,23 +1678,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1068
1678
|
set,
|
|
1069
1679
|
get,
|
|
1070
1680
|
// Mock objects that have to be configured
|
|
1681
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1682
|
+
primaryStore: null,
|
|
1071
1683
|
gl: null,
|
|
1072
1684
|
renderer: null,
|
|
1073
1685
|
camera: null,
|
|
1074
1686
|
frustum: new webgpu.Frustum(),
|
|
1075
1687
|
autoUpdateFrustum: true,
|
|
1076
1688
|
raycaster: null,
|
|
1077
|
-
events: {
|
|
1689
|
+
events: {
|
|
1690
|
+
priority: 1,
|
|
1691
|
+
enabled: true,
|
|
1692
|
+
connected: false,
|
|
1693
|
+
frameTimedRaycasts: true,
|
|
1694
|
+
alwaysFireOnScroll: true,
|
|
1695
|
+
updateOnFrame: false
|
|
1696
|
+
},
|
|
1078
1697
|
scene: null,
|
|
1079
1698
|
rootScene: null,
|
|
1080
1699
|
xr: null,
|
|
1081
1700
|
inspector: null,
|
|
1082
1701
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1083
1702
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1084
|
-
|
|
1085
|
-
linear: false,
|
|
1086
|
-
flat: false,
|
|
1087
|
-
textureColorSpace: "srgb",
|
|
1703
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1088
1704
|
isLegacy: false,
|
|
1089
1705
|
webGPUSupported: false,
|
|
1090
1706
|
isNative: false,
|
|
@@ -1124,10 +1740,40 @@ const createStore = (invalidate, advance) => {
|
|
|
1124
1740
|
getCurrentViewport
|
|
1125
1741
|
},
|
|
1126
1742
|
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
1127
|
-
setSize: (width, height, top
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1743
|
+
setSize: (width, height, top, left) => {
|
|
1744
|
+
const state2 = get();
|
|
1745
|
+
if (width === void 0) {
|
|
1746
|
+
set({ _sizeImperative: false });
|
|
1747
|
+
if (state2._sizeProps) {
|
|
1748
|
+
const { width: propW, height: propH } = state2._sizeProps;
|
|
1749
|
+
if (propW !== void 0 || propH !== void 0) {
|
|
1750
|
+
const currentSize = state2.size;
|
|
1751
|
+
const newSize = {
|
|
1752
|
+
width: propW ?? currentSize.width,
|
|
1753
|
+
height: propH ?? currentSize.height,
|
|
1754
|
+
top: currentSize.top,
|
|
1755
|
+
left: currentSize.left
|
|
1756
|
+
};
|
|
1757
|
+
set((s) => ({
|
|
1758
|
+
size: newSize,
|
|
1759
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1760
|
+
}));
|
|
1761
|
+
scheduler.getScheduler().invalidate();
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const w = width;
|
|
1767
|
+
const h = height ?? width;
|
|
1768
|
+
const t = top ?? state2.size.top;
|
|
1769
|
+
const l = left ?? state2.size.left;
|
|
1770
|
+
const size = { width: w, height: h, top: t, left: l };
|
|
1771
|
+
set((s) => ({
|
|
1772
|
+
size,
|
|
1773
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1774
|
+
_sizeImperative: true
|
|
1775
|
+
}));
|
|
1776
|
+
scheduler.getScheduler().invalidate();
|
|
1131
1777
|
},
|
|
1132
1778
|
setDpr: (dpr) => set((state2) => {
|
|
1133
1779
|
const resolved = calculateDpr(dpr);
|
|
@@ -1138,22 +1784,32 @@ const createStore = (invalidate, advance) => {
|
|
|
1138
1784
|
},
|
|
1139
1785
|
setError: (error) => set(() => ({ error })),
|
|
1140
1786
|
error: null,
|
|
1141
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1787
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1142
1788
|
uniforms: {},
|
|
1143
1789
|
nodes: {},
|
|
1790
|
+
buffers: {},
|
|
1791
|
+
gpuStorage: {},
|
|
1144
1792
|
textures: /* @__PURE__ */ new Map(),
|
|
1145
|
-
|
|
1793
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1794
|
+
renderPipeline: null,
|
|
1146
1795
|
passes: {},
|
|
1796
|
+
_hmrVersion: 0,
|
|
1797
|
+
_sizeImperative: false,
|
|
1798
|
+
_sizeProps: null,
|
|
1147
1799
|
previousRoot: void 0,
|
|
1148
1800
|
internal: {
|
|
1149
1801
|
// Events
|
|
1150
1802
|
interaction: [],
|
|
1151
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1152
1803
|
subscribers: [],
|
|
1804
|
+
// Per-pointer state (new unified structure)
|
|
1805
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1806
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1807
|
+
lastEvent: React__namespace.createRef(),
|
|
1808
|
+
// Deprecated but kept for backwards compatibility
|
|
1809
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1153
1810
|
initialClick: [0, 0],
|
|
1154
1811
|
initialHits: [],
|
|
1155
1812
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1156
|
-
lastEvent: React__namespace.createRef(),
|
|
1157
1813
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1158
1814
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1159
1815
|
// Occlusion system (WebGPU only)
|
|
@@ -1236,13 +1892,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1236
1892
|
rootStore.subscribe(() => {
|
|
1237
1893
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1238
1894
|
const actualRenderer = internal.actualRenderer;
|
|
1895
|
+
const canvasTarget = internal.canvasTarget;
|
|
1239
1896
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1240
1897
|
oldSize = size;
|
|
1241
1898
|
oldDpr = viewport.dpr;
|
|
1242
1899
|
updateCamera(camera, size);
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
1245
|
-
|
|
1900
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1901
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1902
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1903
|
+
} else {
|
|
1904
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1905
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1906
|
+
if (canvasTarget) {
|
|
1907
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1908
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1246
1911
|
}
|
|
1247
1912
|
if (camera !== oldCamera) {
|
|
1248
1913
|
oldCamera = camera;
|
|
@@ -1313,1041 +1978,10 @@ useLoader.clear = function(loader, input) {
|
|
|
1313
1978
|
};
|
|
1314
1979
|
useLoader.loader = getLoader;
|
|
1315
1980
|
|
|
1316
|
-
var __defProp$1 = Object.defineProperty;
|
|
1317
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1318
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1319
|
-
const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
|
|
1320
|
-
class PhaseGraph {
|
|
1321
|
-
constructor() {
|
|
1322
|
-
/** Ordered list of phase nodes */
|
|
1323
|
-
__publicField$1(this, "phases", []);
|
|
1324
|
-
/** Quick lookup by name */
|
|
1325
|
-
__publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1326
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1327
|
-
__publicField$1(this, "orderedNamesCache", null);
|
|
1328
|
-
this.initializeDefaultPhases();
|
|
1329
|
-
}
|
|
1330
|
-
//* Initialization --------------------------------
|
|
1331
|
-
initializeDefaultPhases() {
|
|
1332
|
-
for (const name of DEFAULT_PHASES) {
|
|
1333
|
-
const node = { name, isAutoGenerated: false };
|
|
1334
|
-
this.phases.push(node);
|
|
1335
|
-
this.phaseMap.set(name, node);
|
|
1336
|
-
}
|
|
1337
|
-
this.invalidateCache();
|
|
1338
|
-
}
|
|
1339
|
-
//* Public API --------------------------------
|
|
1340
|
-
/**
|
|
1341
|
-
* Add a named phase to the graph
|
|
1342
|
-
* @param name - Phase name (must be unique)
|
|
1343
|
-
* @param options - Position options (before or after another phase)
|
|
1344
|
-
*/
|
|
1345
|
-
addPhase(name, options = {}) {
|
|
1346
|
-
if (this.phaseMap.has(name)) {
|
|
1347
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
const { before, after } = options;
|
|
1351
|
-
const node = { name, isAutoGenerated: false };
|
|
1352
|
-
let insertIndex = this.phases.length;
|
|
1353
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1354
|
-
if (targetIndex !== -1) insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1355
|
-
else {
|
|
1356
|
-
const constraintType = before ? "before" : "after";
|
|
1357
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1358
|
-
}
|
|
1359
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1360
|
-
this.phaseMap.set(name, node);
|
|
1361
|
-
this.invalidateCache();
|
|
1362
|
-
}
|
|
1363
|
-
/**
|
|
1364
|
-
* Get ordered list of phase names
|
|
1365
|
-
*/
|
|
1366
|
-
getOrderedPhases() {
|
|
1367
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1368
|
-
return this.orderedNamesCache;
|
|
1369
|
-
}
|
|
1370
|
-
/**
|
|
1371
|
-
* Check if a phase exists
|
|
1372
|
-
*/
|
|
1373
|
-
hasPhase(name) {
|
|
1374
|
-
return this.phaseMap.has(name);
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Get the index of a phase (-1 if not found)
|
|
1378
|
-
*/
|
|
1379
|
-
getPhaseIndex(name) {
|
|
1380
|
-
if (!name) return -1;
|
|
1381
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1385
|
-
* Used for resolving before/after constraints.
|
|
1386
|
-
*
|
|
1387
|
-
* @param name - The phase name to ensure exists
|
|
1388
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1389
|
-
*/
|
|
1390
|
-
ensurePhase(name) {
|
|
1391
|
-
if (this.phaseMap.has(name)) return name;
|
|
1392
|
-
const node = { name, isAutoGenerated: true };
|
|
1393
|
-
this.phases.push(node);
|
|
1394
|
-
this.phaseMap.set(name, node);
|
|
1395
|
-
this.invalidateCache();
|
|
1396
|
-
return name;
|
|
1397
|
-
}
|
|
1398
|
-
/**
|
|
1399
|
-
* Resolve where a job with before/after constraints should go.
|
|
1400
|
-
* Creates auto-generated phases if needed.
|
|
1401
|
-
*
|
|
1402
|
-
* @param before - Phase(s) to run before
|
|
1403
|
-
* @param after - Phase(s) to run after
|
|
1404
|
-
* @returns The resolved phase name
|
|
1405
|
-
*/
|
|
1406
|
-
resolveConstraintPhase(before, after) {
|
|
1407
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1408
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1409
|
-
if (beforeArr.length > 0) {
|
|
1410
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1411
|
-
}
|
|
1412
|
-
if (afterArr.length > 0) {
|
|
1413
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1414
|
-
}
|
|
1415
|
-
return "update";
|
|
1416
|
-
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1419
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1420
|
-
*
|
|
1421
|
-
* @param target - The target phase name to position relative to
|
|
1422
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1423
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1424
|
-
* @returns The auto-generated phase name
|
|
1425
|
-
*/
|
|
1426
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1427
|
-
const autoName = `${prefix}:${target}`;
|
|
1428
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1429
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1430
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1431
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1432
|
-
else this.phases.push(node);
|
|
1433
|
-
this.phaseMap.set(autoName, node);
|
|
1434
|
-
this.invalidateCache();
|
|
1435
|
-
return autoName;
|
|
1436
|
-
}
|
|
1437
|
-
// Internal --------------------------------
|
|
1438
|
-
invalidateCache() {
|
|
1439
|
-
this.orderedNamesCache = null;
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1444
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1445
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1446
|
-
for (const phase of orderedPhases) {
|
|
1447
|
-
buckets.set(phase, []);
|
|
1448
|
-
}
|
|
1449
|
-
for (const job of jobs.values()) {
|
|
1450
|
-
if (!job.enabled) continue;
|
|
1451
|
-
let bucket = buckets.get(job.phase);
|
|
1452
|
-
if (!bucket) {
|
|
1453
|
-
bucket = [];
|
|
1454
|
-
buckets.set(job.phase, bucket);
|
|
1455
|
-
}
|
|
1456
|
-
bucket.push(job);
|
|
1457
|
-
}
|
|
1458
|
-
const sortedBuckets = [];
|
|
1459
|
-
for (const phase of orderedPhases) {
|
|
1460
|
-
const bucket = buckets.get(phase);
|
|
1461
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1462
|
-
bucket.sort((a, b) => {
|
|
1463
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1464
|
-
return a.index - b.index;
|
|
1465
|
-
});
|
|
1466
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1467
|
-
}
|
|
1468
|
-
for (const [phase, bucket] of buckets) {
|
|
1469
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1470
|
-
bucket.sort((a, b) => {
|
|
1471
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1472
|
-
return a.index - b.index;
|
|
1473
|
-
});
|
|
1474
|
-
sortedBuckets.push(bucket);
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
return sortedBuckets.flat();
|
|
1478
|
-
}
|
|
1479
|
-
function hasCrossJobConstraints(bucket) {
|
|
1480
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1481
|
-
for (const job of bucket) {
|
|
1482
|
-
for (const ref of job.before) {
|
|
1483
|
-
if (jobIds.has(ref)) return true;
|
|
1484
|
-
}
|
|
1485
|
-
for (const ref of job.after) {
|
|
1486
|
-
if (jobIds.has(ref)) return true;
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return false;
|
|
1490
|
-
}
|
|
1491
|
-
function topologicalSort(jobs) {
|
|
1492
|
-
const n = jobs.length;
|
|
1493
|
-
if (n <= 1) return jobs;
|
|
1494
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1495
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1496
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1497
|
-
for (const job of jobs) {
|
|
1498
|
-
jobMap.set(job.id, job);
|
|
1499
|
-
inDegree.set(job.id, 0);
|
|
1500
|
-
adjacency.set(job.id, []);
|
|
1501
|
-
}
|
|
1502
|
-
for (const job of jobs) {
|
|
1503
|
-
for (const ref of job.before) {
|
|
1504
|
-
if (jobMap.has(ref)) {
|
|
1505
|
-
adjacency.get(job.id).push(ref);
|
|
1506
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
for (const ref of job.after) {
|
|
1510
|
-
if (jobMap.has(ref)) {
|
|
1511
|
-
adjacency.get(ref).push(job.id);
|
|
1512
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
const queue = [];
|
|
1517
|
-
for (const job of jobs) {
|
|
1518
|
-
if (inDegree.get(job.id) === 0) {
|
|
1519
|
-
queue.push(job);
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
queue.sort((a, b) => {
|
|
1523
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1524
|
-
return a.index - b.index;
|
|
1525
|
-
});
|
|
1526
|
-
const result = [];
|
|
1527
|
-
while (queue.length > 0) {
|
|
1528
|
-
const job = queue.shift();
|
|
1529
|
-
result.push(job);
|
|
1530
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1531
|
-
for (const neighborId of neighbors) {
|
|
1532
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1533
|
-
inDegree.set(neighborId, newDegree);
|
|
1534
|
-
if (newDegree === 0) {
|
|
1535
|
-
const neighbor = jobMap.get(neighborId);
|
|
1536
|
-
insertSorted(queue, neighbor);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
if (result.length !== n) {
|
|
1541
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1542
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1543
|
-
for (const job of jobs) {
|
|
1544
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
return result;
|
|
1548
|
-
}
|
|
1549
|
-
function insertSorted(arr, job) {
|
|
1550
|
-
let i = 0;
|
|
1551
|
-
while (i < arr.length) {
|
|
1552
|
-
const cmp = arr[i];
|
|
1553
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
|
-
i++;
|
|
1557
|
-
}
|
|
1558
|
-
arr.splice(i, 0, job);
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
function shouldRun(job, now) {
|
|
1562
|
-
if (!job.enabled) return false;
|
|
1563
|
-
if (!job.fps) return true;
|
|
1564
|
-
const minInterval = 1e3 / job.fps;
|
|
1565
|
-
const lastRun = job.lastRun ?? 0;
|
|
1566
|
-
const elapsed = now - lastRun;
|
|
1567
|
-
if (elapsed < minInterval) return false;
|
|
1568
|
-
if (job.drop) {
|
|
1569
|
-
job.lastRun = now;
|
|
1570
|
-
} else {
|
|
1571
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1572
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1573
|
-
if (job.lastRun < now - minInterval) {
|
|
1574
|
-
job.lastRun = now;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
return true;
|
|
1578
|
-
}
|
|
1579
|
-
function resetJobTiming(job) {
|
|
1580
|
-
job.lastRun = void 0;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
var __defProp = Object.defineProperty;
|
|
1584
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1585
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1586
|
-
const hmrData = (() => {
|
|
1587
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1588
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1589
|
-
try {
|
|
1590
|
-
return (0, eval)("import.meta.hot");
|
|
1591
|
-
} catch {
|
|
1592
|
-
return void 0;
|
|
1593
|
-
}
|
|
1594
|
-
})();
|
|
1595
|
-
const _Scheduler = class _Scheduler {
|
|
1596
|
-
//* Constructor ================================
|
|
1597
|
-
constructor() {
|
|
1598
|
-
//* Critical State ================================
|
|
1599
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1600
|
-
__publicField(this, "phaseGraph");
|
|
1601
|
-
__publicField(this, "loopState", {
|
|
1602
|
-
running: false,
|
|
1603
|
-
rafHandle: null,
|
|
1604
|
-
lastTime: null,
|
|
1605
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1606
|
-
frameCount: 0,
|
|
1607
|
-
elapsedTime: 0,
|
|
1608
|
-
createdAt: performance.now()
|
|
1609
|
-
});
|
|
1610
|
-
__publicField(this, "stoppedTime", 0);
|
|
1611
|
-
//* Private State ================================
|
|
1612
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1613
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1614
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1615
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1616
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1617
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1618
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1619
|
-
__publicField(this, "pendingFrames", 0);
|
|
1620
|
-
__publicField(this, "_frameloop", "always");
|
|
1621
|
-
//* Independent Mode & Error Handling State ================================
|
|
1622
|
-
__publicField(this, "_independent", false);
|
|
1623
|
-
__publicField(this, "errorHandler", null);
|
|
1624
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1625
|
-
//* Core Loop Execution Methods ================================
|
|
1626
|
-
/**
|
|
1627
|
-
* Main RAF loop callback.
|
|
1628
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1629
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1630
|
-
* @returns {void}
|
|
1631
|
-
* @private
|
|
1632
|
-
*/
|
|
1633
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1634
|
-
if (!this.loopState.running) return;
|
|
1635
|
-
this.executeFrame(timestamp);
|
|
1636
|
-
if (this._frameloop === "demand") {
|
|
1637
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1638
|
-
if (this.pendingFrames === 0) {
|
|
1639
|
-
this.notifyIdle(timestamp);
|
|
1640
|
-
return this.stop();
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1644
|
-
});
|
|
1645
|
-
this.phaseGraph = new PhaseGraph();
|
|
1646
|
-
}
|
|
1647
|
-
static get instance() {
|
|
1648
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1649
|
-
}
|
|
1650
|
-
static set instance(value) {
|
|
1651
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1652
|
-
}
|
|
1653
|
-
/**
|
|
1654
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1655
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1656
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1657
|
-
*/
|
|
1658
|
-
static get() {
|
|
1659
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1660
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1661
|
-
}
|
|
1662
|
-
if (!_Scheduler.instance) {
|
|
1663
|
-
_Scheduler.instance = new _Scheduler();
|
|
1664
|
-
if (hmrData?.data) {
|
|
1665
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
return _Scheduler.instance;
|
|
1669
|
-
}
|
|
1670
|
-
/**
|
|
1671
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1672
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1673
|
-
* @returns {void}
|
|
1674
|
-
*/
|
|
1675
|
-
static reset() {
|
|
1676
|
-
if (_Scheduler.instance) {
|
|
1677
|
-
_Scheduler.instance.stop();
|
|
1678
|
-
_Scheduler.instance = null;
|
|
1679
|
-
}
|
|
1680
|
-
if (hmrData?.data) {
|
|
1681
|
-
hmrData.data.scheduler = null;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
//* Getters & Setters ================================
|
|
1685
|
-
get phases() {
|
|
1686
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1687
|
-
}
|
|
1688
|
-
get frameloop() {
|
|
1689
|
-
return this._frameloop;
|
|
1690
|
-
}
|
|
1691
|
-
set frameloop(mode) {
|
|
1692
|
-
if (this._frameloop === mode) return;
|
|
1693
|
-
const wasAlways = this._frameloop === "always";
|
|
1694
|
-
this._frameloop = mode;
|
|
1695
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1696
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1697
|
-
}
|
|
1698
|
-
get isRunning() {
|
|
1699
|
-
return this.loopState.running;
|
|
1700
|
-
}
|
|
1701
|
-
get isReady() {
|
|
1702
|
-
return this.roots.size > 0;
|
|
1703
|
-
}
|
|
1704
|
-
get independent() {
|
|
1705
|
-
return this._independent;
|
|
1706
|
-
}
|
|
1707
|
-
set independent(value) {
|
|
1708
|
-
this._independent = value;
|
|
1709
|
-
if (value) this.ensureDefaultRoot();
|
|
1710
|
-
}
|
|
1711
|
-
//* Root Management Methods ================================
|
|
1712
|
-
/**
|
|
1713
|
-
* Register a root (Canvas) with the scheduler.
|
|
1714
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1715
|
-
* @param {string} id - Unique identifier for this root
|
|
1716
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1717
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1718
|
-
*/
|
|
1719
|
-
registerRoot(id, options = {}) {
|
|
1720
|
-
if (this.roots.has(id)) {
|
|
1721
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1722
|
-
return () => this.unregisterRoot(id);
|
|
1723
|
-
}
|
|
1724
|
-
const entry = {
|
|
1725
|
-
id,
|
|
1726
|
-
getState: options.getState ?? (() => ({})),
|
|
1727
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1728
|
-
sortedJobs: [],
|
|
1729
|
-
needsRebuild: false
|
|
1730
|
-
};
|
|
1731
|
-
if (options.onError) {
|
|
1732
|
-
this.errorHandler = options.onError;
|
|
1733
|
-
}
|
|
1734
|
-
this.roots.set(id, entry);
|
|
1735
|
-
if (this.roots.size === 1) {
|
|
1736
|
-
this.notifyRootReady();
|
|
1737
|
-
if (this._frameloop === "always") this.start();
|
|
1738
|
-
}
|
|
1739
|
-
return () => this.unregisterRoot(id);
|
|
1740
|
-
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Unregister a root from the scheduler.
|
|
1743
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1744
|
-
* The last root to unregister stops the RAF loop.
|
|
1745
|
-
* @param {string} id - The root ID to unregister
|
|
1746
|
-
* @returns {void}
|
|
1747
|
-
*/
|
|
1748
|
-
unregisterRoot(id) {
|
|
1749
|
-
const root = this.roots.get(id);
|
|
1750
|
-
if (!root) return;
|
|
1751
|
-
for (const jobId of root.jobs.keys()) {
|
|
1752
|
-
this.jobStateListeners.delete(jobId);
|
|
1753
|
-
}
|
|
1754
|
-
this.roots.delete(id);
|
|
1755
|
-
if (this.roots.size === 0) {
|
|
1756
|
-
this.stop();
|
|
1757
|
-
this.errorHandler = null;
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
/**
|
|
1761
|
-
* Subscribe to be notified when a root becomes available.
|
|
1762
|
-
* Fires immediately if a root already exists.
|
|
1763
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1764
|
-
* @returns {() => void} Unsubscribe function
|
|
1765
|
-
*/
|
|
1766
|
-
onRootReady(callback) {
|
|
1767
|
-
if (this.roots.size > 0) {
|
|
1768
|
-
callback();
|
|
1769
|
-
return () => {
|
|
1770
|
-
};
|
|
1771
|
-
}
|
|
1772
|
-
this.rootReadyCallbacks.add(callback);
|
|
1773
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1774
|
-
}
|
|
1775
|
-
/**
|
|
1776
|
-
* Notify all registered root-ready callbacks.
|
|
1777
|
-
* Called when the first root registers.
|
|
1778
|
-
* @returns {void}
|
|
1779
|
-
* @private
|
|
1780
|
-
*/
|
|
1781
|
-
notifyRootReady() {
|
|
1782
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1783
|
-
try {
|
|
1784
|
-
cb();
|
|
1785
|
-
} catch (error) {
|
|
1786
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
this.rootReadyCallbacks.clear();
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* Ensure a default root exists for independent mode.
|
|
1793
|
-
* Creates a minimal root with no state provider.
|
|
1794
|
-
* @returns {void}
|
|
1795
|
-
* @private
|
|
1796
|
-
*/
|
|
1797
|
-
ensureDefaultRoot() {
|
|
1798
|
-
if (!this.roots.has("__default__")) {
|
|
1799
|
-
this.registerRoot("__default__");
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
/**
|
|
1803
|
-
* Trigger error handling for job errors.
|
|
1804
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1805
|
-
* @param {Error} error - The error to handle
|
|
1806
|
-
* @returns {void}
|
|
1807
|
-
*/
|
|
1808
|
-
triggerError(error) {
|
|
1809
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1810
|
-
else console.error("[Scheduler]", error);
|
|
1811
|
-
}
|
|
1812
|
-
//* Phase Management Methods ================================
|
|
1813
|
-
/**
|
|
1814
|
-
* Add a named phase to the scheduler's execution order.
|
|
1815
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1816
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1817
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1818
|
-
* @returns {void}
|
|
1819
|
-
* @example
|
|
1820
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1821
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1822
|
-
*/
|
|
1823
|
-
addPhase(name, options) {
|
|
1824
|
-
this.phaseGraph.addPhase(name, options);
|
|
1825
|
-
for (const root of this.roots.values()) {
|
|
1826
|
-
root.needsRebuild = true;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
/**
|
|
1830
|
-
* Check if a phase exists in the scheduler.
|
|
1831
|
-
* @param {string} name - The phase name to check
|
|
1832
|
-
* @returns {boolean} True if the phase exists
|
|
1833
|
-
*/
|
|
1834
|
-
hasPhase(name) {
|
|
1835
|
-
return this.phaseGraph.hasPhase(name);
|
|
1836
|
-
}
|
|
1837
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1838
|
-
/**
|
|
1839
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1840
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1841
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1842
|
-
* @param {string} id - Unique identifier for this global job
|
|
1843
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1844
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1845
|
-
* @deprecated Use useFrame with phases instead
|
|
1846
|
-
*/
|
|
1847
|
-
registerGlobal(phase, id, callback) {
|
|
1848
|
-
const job = { id, callback };
|
|
1849
|
-
if (phase === "before") {
|
|
1850
|
-
this.globalBeforeJobs.set(id, job);
|
|
1851
|
-
} else {
|
|
1852
|
-
this.globalAfterJobs.set(id, job);
|
|
1853
|
-
}
|
|
1854
|
-
return () => {
|
|
1855
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1856
|
-
else this.globalAfterJobs.delete(id);
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1860
|
-
/**
|
|
1861
|
-
* Register an idle callback that fires when the loop stops.
|
|
1862
|
-
* Used internally by deprecated addTail API.
|
|
1863
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1864
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1865
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1866
|
-
*/
|
|
1867
|
-
onIdle(callback) {
|
|
1868
|
-
this.idleCallbacks.add(callback);
|
|
1869
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1870
|
-
}
|
|
1871
|
-
/**
|
|
1872
|
-
* Notify all registered idle callbacks.
|
|
1873
|
-
* Called when the loop stops in demand mode.
|
|
1874
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1875
|
-
* @returns {void}
|
|
1876
|
-
* @private
|
|
1877
|
-
*/
|
|
1878
|
-
notifyIdle(timestamp) {
|
|
1879
|
-
for (const cb of this.idleCallbacks) {
|
|
1880
|
-
try {
|
|
1881
|
-
cb(timestamp);
|
|
1882
|
-
} catch (error) {
|
|
1883
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
//* Job Registration & Management Methods ================================
|
|
1888
|
-
/**
|
|
1889
|
-
* Register a job (frame callback) with a specific root.
|
|
1890
|
-
* This is the core registration method used by useFrame internally.
|
|
1891
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1892
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1893
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1894
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1895
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1896
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1897
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1898
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1899
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1900
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1901
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1902
|
-
*/
|
|
1903
|
-
register(callback, options = {}) {
|
|
1904
|
-
const rootId = options.rootId;
|
|
1905
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1906
|
-
if (!root) {
|
|
1907
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1908
|
-
return () => {
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
const id = options.id ?? this.generateJobId();
|
|
1912
|
-
let phase = options.phase ?? "update";
|
|
1913
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1914
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1915
|
-
}
|
|
1916
|
-
const before = this.normalizeConstraints(options.before);
|
|
1917
|
-
const after = this.normalizeConstraints(options.after);
|
|
1918
|
-
const job = {
|
|
1919
|
-
id,
|
|
1920
|
-
callback,
|
|
1921
|
-
phase,
|
|
1922
|
-
before,
|
|
1923
|
-
after,
|
|
1924
|
-
priority: options.priority ?? 0,
|
|
1925
|
-
index: this.nextJobIndex++,
|
|
1926
|
-
fps: options.fps,
|
|
1927
|
-
drop: options.drop ?? true,
|
|
1928
|
-
enabled: options.enabled ?? true,
|
|
1929
|
-
system: options.system ?? false
|
|
1930
|
-
};
|
|
1931
|
-
if (root.jobs.has(id)) {
|
|
1932
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1933
|
-
}
|
|
1934
|
-
root.jobs.set(id, job);
|
|
1935
|
-
root.needsRebuild = true;
|
|
1936
|
-
return () => this.unregister(id, root.id);
|
|
1937
|
-
}
|
|
1938
|
-
/**
|
|
1939
|
-
* Unregister a job by its ID.
|
|
1940
|
-
* Searches all roots if rootId is not provided.
|
|
1941
|
-
* @param {string} id - The job ID to unregister
|
|
1942
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1943
|
-
* @returns {void}
|
|
1944
|
-
*/
|
|
1945
|
-
unregister(id, rootId) {
|
|
1946
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1947
|
-
if (root?.jobs.delete(id)) {
|
|
1948
|
-
root.needsRebuild = true;
|
|
1949
|
-
this.jobStateListeners.delete(id);
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
/**
|
|
1953
|
-
* Update a job's options dynamically.
|
|
1954
|
-
* Searches all roots to find the job by ID.
|
|
1955
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1956
|
-
* @param {string} id - The job ID to update
|
|
1957
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1958
|
-
* @returns {void}
|
|
1959
|
-
*/
|
|
1960
|
-
updateJob(id, options) {
|
|
1961
|
-
let job;
|
|
1962
|
-
let root;
|
|
1963
|
-
for (const r of this.roots.values()) {
|
|
1964
|
-
job = r.jobs.get(id);
|
|
1965
|
-
if (job) {
|
|
1966
|
-
root = r;
|
|
1967
|
-
break;
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
if (!job || !root) return;
|
|
1971
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
1972
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
1973
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
1974
|
-
if (options.enabled !== void 0) {
|
|
1975
|
-
const wasEnabled = job.enabled;
|
|
1976
|
-
job.enabled = options.enabled;
|
|
1977
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
1978
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
1979
|
-
}
|
|
1980
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
1981
|
-
if (options.phase) job.phase = options.phase;
|
|
1982
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
1983
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
1984
|
-
root.needsRebuild = true;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
//* Job State Management Methods ================================
|
|
1988
|
-
/**
|
|
1989
|
-
* Check if a job is currently paused (disabled).
|
|
1990
|
-
* @param {string} id - The job ID to check
|
|
1991
|
-
* @returns {boolean} True if the job exists and is paused
|
|
1992
|
-
*/
|
|
1993
|
-
isJobPaused(id) {
|
|
1994
|
-
for (const root of this.roots.values()) {
|
|
1995
|
-
const job = root.jobs.get(id);
|
|
1996
|
-
if (job) return !job.enabled;
|
|
1997
|
-
}
|
|
1998
|
-
return false;
|
|
1999
|
-
}
|
|
2000
|
-
/**
|
|
2001
|
-
* Subscribe to state changes for a specific job.
|
|
2002
|
-
* Listener is called when job is paused or resumed.
|
|
2003
|
-
* @param {string} id - The job ID to subscribe to
|
|
2004
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2005
|
-
* @returns {() => void} Unsubscribe function
|
|
2006
|
-
*/
|
|
2007
|
-
subscribeJobState(id, listener) {
|
|
2008
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2009
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2010
|
-
}
|
|
2011
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2012
|
-
return () => {
|
|
2013
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2014
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2015
|
-
this.jobStateListeners.delete(id);
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
/**
|
|
2020
|
-
* Notify all listeners that a job's state has changed.
|
|
2021
|
-
* @param {string} id - The job ID that changed
|
|
2022
|
-
* @returns {void}
|
|
2023
|
-
* @private
|
|
2024
|
-
*/
|
|
2025
|
-
notifyJobStateChange(id) {
|
|
2026
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Pause a job by ID (sets enabled=false).
|
|
2030
|
-
* Notifies any subscribed state listeners.
|
|
2031
|
-
* @param {string} id - The job ID to pause
|
|
2032
|
-
* @returns {void}
|
|
2033
|
-
*/
|
|
2034
|
-
pauseJob(id) {
|
|
2035
|
-
this.updateJob(id, { enabled: false });
|
|
2036
|
-
this.notifyJobStateChange(id);
|
|
2037
|
-
}
|
|
2038
|
-
/**
|
|
2039
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2040
|
-
* Resets job timing to prevent frame accumulation.
|
|
2041
|
-
* Notifies any subscribed state listeners.
|
|
2042
|
-
* @param {string} id - The job ID to resume
|
|
2043
|
-
* @returns {void}
|
|
2044
|
-
*/
|
|
2045
|
-
resumeJob(id) {
|
|
2046
|
-
this.updateJob(id, { enabled: true });
|
|
2047
|
-
this.notifyJobStateChange(id);
|
|
2048
|
-
}
|
|
2049
|
-
//* Frame Loop Control Methods ================================
|
|
2050
|
-
/**
|
|
2051
|
-
* Start the requestAnimationFrame loop.
|
|
2052
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2053
|
-
* No-op if already running.
|
|
2054
|
-
* @returns {void}
|
|
2055
|
-
*/
|
|
2056
|
-
start() {
|
|
2057
|
-
if (this.loopState.running) return;
|
|
2058
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2059
|
-
let adjustedCreated = 0;
|
|
2060
|
-
if (this.stoppedTime > 0) {
|
|
2061
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2062
|
-
this.stoppedTime = 0;
|
|
2063
|
-
}
|
|
2064
|
-
Object.assign(this.loopState, {
|
|
2065
|
-
running: true,
|
|
2066
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2067
|
-
lastTime: performance.now(),
|
|
2068
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2069
|
-
frameCount: 0,
|
|
2070
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2071
|
-
});
|
|
2072
|
-
}
|
|
2073
|
-
/**
|
|
2074
|
-
* Stop the requestAnimationFrame loop.
|
|
2075
|
-
* Cancels any pending RAF callback.
|
|
2076
|
-
* No-op if not running.
|
|
2077
|
-
* @returns {void}
|
|
2078
|
-
*/
|
|
2079
|
-
stop() {
|
|
2080
|
-
if (!this.loopState.running) return;
|
|
2081
|
-
this.loopState.running = false;
|
|
2082
|
-
if (this.loopState.rafHandle !== null) {
|
|
2083
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2084
|
-
this.loopState.rafHandle = null;
|
|
2085
|
-
}
|
|
2086
|
-
this.stoppedTime = performance.now();
|
|
2087
|
-
}
|
|
2088
|
-
/**
|
|
2089
|
-
* Request frames to be rendered in demand mode.
|
|
2090
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2091
|
-
* No-op if frameloop is not 'demand'.
|
|
2092
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2093
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2094
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2095
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2096
|
-
* @returns {void}
|
|
2097
|
-
* @example
|
|
2098
|
-
* // Request a single frame render
|
|
2099
|
-
* scheduler.invalidate();
|
|
2100
|
-
*
|
|
2101
|
-
* @example
|
|
2102
|
-
* // Request 5 frames (e.g., for animations)
|
|
2103
|
-
* scheduler.invalidate(5);
|
|
2104
|
-
*
|
|
2105
|
-
* @example
|
|
2106
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2107
|
-
* scheduler.invalidate(3, false);
|
|
2108
|
-
*
|
|
2109
|
-
* @example
|
|
2110
|
-
* // Add 2 more frames to existing pending count
|
|
2111
|
-
* scheduler.invalidate(2, true);
|
|
2112
|
-
*/
|
|
2113
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2114
|
-
if (this._frameloop !== "demand") return;
|
|
2115
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2116
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2117
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2118
|
-
}
|
|
2119
|
-
/**
|
|
2120
|
-
* Reset timing state for deterministic testing.
|
|
2121
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2122
|
-
* @returns {void}
|
|
2123
|
-
*/
|
|
2124
|
-
resetTiming() {
|
|
2125
|
-
this.loopState.lastTime = null;
|
|
2126
|
-
this.loopState.frameCount = 0;
|
|
2127
|
-
this.loopState.elapsedTime = 0;
|
|
2128
|
-
this.loopState.createdAt = performance.now();
|
|
2129
|
-
}
|
|
2130
|
-
//* Manual Stepping Methods ================================
|
|
2131
|
-
/**
|
|
2132
|
-
* Manually execute a single frame for all roots.
|
|
2133
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2134
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2135
|
-
* @returns {void}
|
|
2136
|
-
* @example
|
|
2137
|
-
* // Manual control mode
|
|
2138
|
-
* scheduler.frameloop = 'never';
|
|
2139
|
-
* scheduler.step(); // Execute one frame
|
|
2140
|
-
*/
|
|
2141
|
-
step(timestamp) {
|
|
2142
|
-
const now = timestamp ?? performance.now();
|
|
2143
|
-
this.executeFrame(now);
|
|
2144
|
-
}
|
|
2145
|
-
/**
|
|
2146
|
-
* Manually execute a single job by its ID.
|
|
2147
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2148
|
-
* @param {string} id - The job ID to step
|
|
2149
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2150
|
-
* @returns {void}
|
|
2151
|
-
*/
|
|
2152
|
-
stepJob(id, timestamp) {
|
|
2153
|
-
let job;
|
|
2154
|
-
let root;
|
|
2155
|
-
for (const r of this.roots.values()) {
|
|
2156
|
-
job = r.jobs.get(id);
|
|
2157
|
-
if (job) {
|
|
2158
|
-
root = r;
|
|
2159
|
-
break;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
if (!job || !root) {
|
|
2163
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2164
|
-
return;
|
|
2165
|
-
}
|
|
2166
|
-
const now = timestamp ?? performance.now();
|
|
2167
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2168
|
-
const delta = deltaMs / 1e3;
|
|
2169
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2170
|
-
const providedState = root.getState?.() ?? {};
|
|
2171
|
-
const frameState = {
|
|
2172
|
-
...providedState,
|
|
2173
|
-
time: now,
|
|
2174
|
-
delta,
|
|
2175
|
-
elapsed,
|
|
2176
|
-
frame: this.loopState.frameCount
|
|
2177
|
-
};
|
|
2178
|
-
try {
|
|
2179
|
-
job.callback(frameState, delta);
|
|
2180
|
-
} catch (error) {
|
|
2181
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2182
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
/**
|
|
2186
|
-
* Execute a single frame across all roots.
|
|
2187
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2188
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2189
|
-
* @returns {void}
|
|
2190
|
-
* @private
|
|
2191
|
-
*/
|
|
2192
|
-
executeFrame(timestamp) {
|
|
2193
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2194
|
-
const delta = deltaMs / 1e3;
|
|
2195
|
-
this.loopState.lastTime = timestamp;
|
|
2196
|
-
this.loopState.frameCount++;
|
|
2197
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2198
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2199
|
-
for (const root of this.roots.values()) {
|
|
2200
|
-
this.tickRoot(root, timestamp, delta);
|
|
2201
|
-
}
|
|
2202
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2203
|
-
}
|
|
2204
|
-
/**
|
|
2205
|
-
* Run all global jobs from a job map.
|
|
2206
|
-
* Catches and logs errors without stopping execution.
|
|
2207
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2208
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2209
|
-
* @returns {void}
|
|
2210
|
-
* @private
|
|
2211
|
-
*/
|
|
2212
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2213
|
-
for (const job of jobs.values()) {
|
|
2214
|
-
try {
|
|
2215
|
-
job.callback(timestamp);
|
|
2216
|
-
} catch (error) {
|
|
2217
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
/**
|
|
2222
|
-
* Execute all jobs for a single root in sorted order.
|
|
2223
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2224
|
-
* Errors are caught and propagated via triggerError.
|
|
2225
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2226
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2227
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2228
|
-
* @returns {void}
|
|
2229
|
-
* @private
|
|
2230
|
-
*/
|
|
2231
|
-
tickRoot(root, timestamp, delta) {
|
|
2232
|
-
if (root.needsRebuild) {
|
|
2233
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2234
|
-
root.needsRebuild = false;
|
|
2235
|
-
}
|
|
2236
|
-
const providedState = root.getState?.() ?? {};
|
|
2237
|
-
const frameState = {
|
|
2238
|
-
...providedState,
|
|
2239
|
-
time: timestamp,
|
|
2240
|
-
delta,
|
|
2241
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2242
|
-
// Convert ms to seconds
|
|
2243
|
-
frame: this.loopState.frameCount
|
|
2244
|
-
};
|
|
2245
|
-
for (const job of root.sortedJobs) {
|
|
2246
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2247
|
-
try {
|
|
2248
|
-
job.callback(frameState, delta);
|
|
2249
|
-
} catch (error) {
|
|
2250
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2251
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
//* Debug & Inspection Methods ================================
|
|
2256
|
-
/**
|
|
2257
|
-
* Get the total number of registered jobs across all roots.
|
|
2258
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2259
|
-
* @returns {number} Total job count
|
|
2260
|
-
*/
|
|
2261
|
-
getJobCount() {
|
|
2262
|
-
let count = 0;
|
|
2263
|
-
for (const root of this.roots.values()) {
|
|
2264
|
-
count += root.jobs.size;
|
|
2265
|
-
}
|
|
2266
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2267
|
-
}
|
|
2268
|
-
/**
|
|
2269
|
-
* Get all registered job IDs across all roots.
|
|
2270
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2271
|
-
* @returns {string[]} Array of all job IDs
|
|
2272
|
-
*/
|
|
2273
|
-
getJobIds() {
|
|
2274
|
-
const ids = [];
|
|
2275
|
-
for (const root of this.roots.values()) {
|
|
2276
|
-
ids.push(...root.jobs.keys());
|
|
2277
|
-
}
|
|
2278
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2279
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2280
|
-
return ids;
|
|
2281
|
-
}
|
|
2282
|
-
/**
|
|
2283
|
-
* Get the number of registered roots (Canvas instances).
|
|
2284
|
-
* @returns {number} Number of registered roots
|
|
2285
|
-
*/
|
|
2286
|
-
getRootCount() {
|
|
2287
|
-
return this.roots.size;
|
|
2288
|
-
}
|
|
2289
|
-
/**
|
|
2290
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2291
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2292
|
-
*
|
|
2293
|
-
* @param phase The phase to check
|
|
2294
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2295
|
-
* @returns true if any user jobs exist in the phase
|
|
2296
|
-
*/
|
|
2297
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2298
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2299
|
-
return rootsToCheck.some((root) => {
|
|
2300
|
-
if (!root) return false;
|
|
2301
|
-
for (const job of root.jobs.values()) {
|
|
2302
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2303
|
-
}
|
|
2304
|
-
return false;
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
//* Utility Methods ================================
|
|
2308
|
-
/**
|
|
2309
|
-
* Generate a unique root ID for automatic root registration.
|
|
2310
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2311
|
-
*/
|
|
2312
|
-
generateRootId() {
|
|
2313
|
-
return `root_${this.nextRootIndex++}`;
|
|
2314
|
-
}
|
|
2315
|
-
/**
|
|
2316
|
-
* Generate a unique job ID.
|
|
2317
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2318
|
-
* @private
|
|
2319
|
-
*/
|
|
2320
|
-
generateJobId() {
|
|
2321
|
-
return `job_${this.nextJobIndex}`;
|
|
2322
|
-
}
|
|
2323
|
-
/**
|
|
2324
|
-
* Normalize before/after constraints to a Set.
|
|
2325
|
-
* Handles undefined, single string, or array inputs.
|
|
2326
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2327
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2328
|
-
* @private
|
|
2329
|
-
*/
|
|
2330
|
-
normalizeConstraints(value) {
|
|
2331
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2332
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2333
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2334
|
-
}
|
|
2335
|
-
};
|
|
2336
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2337
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2338
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2339
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2340
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2341
|
-
let Scheduler = _Scheduler;
|
|
2342
|
-
const getScheduler = () => Scheduler.get();
|
|
2343
|
-
if (hmrData) {
|
|
2344
|
-
hmrData.accept?.();
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
1981
|
function useFrame(callback, priorityOrOptions) {
|
|
2348
1982
|
const store = React__namespace.useContext(context);
|
|
2349
1983
|
const isInsideCanvas = store !== null;
|
|
2350
|
-
const scheduler = getScheduler();
|
|
1984
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
2351
1985
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2352
1986
|
id: priorityOrOptions.id,
|
|
2353
1987
|
phase: priorityOrOptions.phase,
|
|
@@ -2395,7 +2029,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2395
2029
|
};
|
|
2396
2030
|
callbackRef.current?.(mergedState, delta);
|
|
2397
2031
|
};
|
|
2398
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2032
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2399
2033
|
id,
|
|
2400
2034
|
rootId,
|
|
2401
2035
|
...options
|
|
@@ -2416,37 +2050,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2416
2050
|
}
|
|
2417
2051
|
};
|
|
2418
2052
|
} else {
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
unregisterJob = registerOutside();
|
|
2428
|
-
});
|
|
2429
|
-
return () => {
|
|
2430
|
-
unsubReady();
|
|
2431
|
-
unregisterJob?.();
|
|
2432
|
-
};
|
|
2053
|
+
return scheduler$1.register(
|
|
2054
|
+
(state, delta) => {
|
|
2055
|
+
const frameState = state;
|
|
2056
|
+
if (!frameState.renderer) return;
|
|
2057
|
+
callbackRef.current?.(frameState, delta);
|
|
2058
|
+
},
|
|
2059
|
+
{ id, ...options }
|
|
2060
|
+
);
|
|
2433
2061
|
}
|
|
2434
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2062
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2435
2063
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2436
2064
|
// Subscribe function
|
|
2437
2065
|
React__namespace.useCallback(
|
|
2438
2066
|
(onStoreChange) => {
|
|
2439
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2067
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2440
2068
|
},
|
|
2441
2069
|
[id]
|
|
2442
2070
|
),
|
|
2443
2071
|
// getSnapshot function
|
|
2444
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2072
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2445
2073
|
// getServerSnapshot function (SSR)
|
|
2446
2074
|
React__namespace.useCallback(() => false, [])
|
|
2447
2075
|
);
|
|
2448
2076
|
const controls = React__namespace.useMemo(() => {
|
|
2449
|
-
const scheduler2 = getScheduler();
|
|
2077
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2450
2078
|
return {
|
|
2451
2079
|
/** The job's unique ID */
|
|
2452
2080
|
id,
|
|
@@ -2461,7 +2089,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2461
2089
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2462
2090
|
*/
|
|
2463
2091
|
step: (timestamp) => {
|
|
2464
|
-
getScheduler().stepJob(id, timestamp);
|
|
2092
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2465
2093
|
},
|
|
2466
2094
|
/**
|
|
2467
2095
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2469,20 +2097,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2469
2097
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2470
2098
|
*/
|
|
2471
2099
|
stepAll: (timestamp) => {
|
|
2472
|
-
getScheduler().step(timestamp);
|
|
2100
|
+
scheduler.getScheduler().step(timestamp);
|
|
2473
2101
|
},
|
|
2474
2102
|
/**
|
|
2475
2103
|
* Pause this job (set enabled=false).
|
|
2476
2104
|
* Job remains registered but won't run.
|
|
2477
2105
|
*/
|
|
2478
2106
|
pause: () => {
|
|
2479
|
-
getScheduler().pauseJob(id);
|
|
2107
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2480
2108
|
},
|
|
2481
2109
|
/**
|
|
2482
2110
|
* Resume this job (set enabled=true).
|
|
2483
2111
|
*/
|
|
2484
2112
|
resume: () => {
|
|
2485
|
-
getScheduler().resumeJob(id);
|
|
2113
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2486
2114
|
},
|
|
2487
2115
|
/**
|
|
2488
2116
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2520,22 +2148,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2520
2148
|
function useTexture(input, optionsOrOnLoad) {
|
|
2521
2149
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2522
2150
|
const store = useStore();
|
|
2523
|
-
const textureCache = useThree((state) => state.textures);
|
|
2524
2151
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2525
|
-
const { onLoad, cache =
|
|
2152
|
+
const { onLoad, cache = true } = options;
|
|
2153
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2154
|
+
onLoadRef.current = onLoad;
|
|
2155
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2526
2156
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2527
2157
|
const cachedResult = React.useMemo(() => {
|
|
2528
2158
|
if (!cache) return null;
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2159
|
+
const textures = store.getState().textures;
|
|
2160
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2161
|
+
return buildFromCache(input, textures);
|
|
2162
|
+
}, [cache, urls, input, store]);
|
|
2532
2163
|
const loadedTextures = useLoader(
|
|
2533
2164
|
webgpu.TextureLoader,
|
|
2534
2165
|
IsObject(input) ? Object.values(input) : input
|
|
2535
2166
|
);
|
|
2167
|
+
const inputKey = urls.join("\0");
|
|
2536
2168
|
React.useLayoutEffect(() => {
|
|
2537
|
-
if (
|
|
2538
|
-
|
|
2169
|
+
if (cachedResult) return;
|
|
2170
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2171
|
+
onLoadCalledForRef.current = inputKey;
|
|
2172
|
+
onLoadRef.current?.(loadedTextures);
|
|
2173
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2539
2174
|
React.useEffect(() => {
|
|
2540
2175
|
if (cachedResult) return;
|
|
2541
2176
|
if ("initTexture" in renderer) {
|
|
@@ -2568,8 +2203,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2568
2203
|
}, [input, loadedTextures, cachedResult]);
|
|
2569
2204
|
React.useEffect(() => {
|
|
2570
2205
|
if (!cache) return;
|
|
2571
|
-
if (cachedResult) return;
|
|
2572
|
-
const set = store.setState;
|
|
2573
2206
|
const urlTextureMap = [];
|
|
2574
2207
|
if (typeof input === "string") {
|
|
2575
2208
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2583,18 +2216,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2583
2216
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2584
2217
|
}
|
|
2585
2218
|
}
|
|
2586
|
-
|
|
2587
|
-
const
|
|
2588
|
-
let
|
|
2219
|
+
store.setState((state) => {
|
|
2220
|
+
const refs = new Map(state._textureRefs);
|
|
2221
|
+
let textures = state.textures;
|
|
2222
|
+
let added = false;
|
|
2589
2223
|
for (const [url, texture] of urlTextureMap) {
|
|
2590
|
-
if (!
|
|
2591
|
-
|
|
2592
|
-
|
|
2224
|
+
if (!textures.has(url)) {
|
|
2225
|
+
if (!added) {
|
|
2226
|
+
textures = new Map(textures);
|
|
2227
|
+
added = true;
|
|
2228
|
+
}
|
|
2229
|
+
textures.set(url, texture);
|
|
2593
2230
|
}
|
|
2231
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2594
2232
|
}
|
|
2595
|
-
return
|
|
2233
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2234
|
+
});
|
|
2235
|
+
return () => store.setState((state) => {
|
|
2236
|
+
const refs = new Map(state._textureRefs);
|
|
2237
|
+
for (const [url] of urlTextureMap) {
|
|
2238
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2239
|
+
if (next <= 0) refs.delete(url);
|
|
2240
|
+
else refs.set(url, next);
|
|
2241
|
+
}
|
|
2242
|
+
return { _textureRefs: refs };
|
|
2596
2243
|
});
|
|
2597
|
-
}, [cache, input, mappedTextures, store
|
|
2244
|
+
}, [cache, input, mappedTextures, store]);
|
|
2598
2245
|
return mappedTextures;
|
|
2599
2246
|
}
|
|
2600
2247
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2610,108 +2257,92 @@ const Texture = ({
|
|
|
2610
2257
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2611
2258
|
};
|
|
2612
2259
|
|
|
2613
|
-
function
|
|
2614
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
2615
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
2616
|
-
return entry.value;
|
|
2617
|
-
}
|
|
2618
|
-
return null;
|
|
2619
|
-
}
|
|
2620
|
-
function useTextures() {
|
|
2260
|
+
function useTextures(selector) {
|
|
2621
2261
|
const store = useStore();
|
|
2622
|
-
|
|
2623
|
-
const set = store.setState;
|
|
2262
|
+
const registry = React.useMemo(() => {
|
|
2624
2263
|
const getState = store.getState;
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
const newMap = new Map(state.textures);
|
|
2628
|
-
newMap.set(key, value);
|
|
2629
|
-
return { textures: newMap };
|
|
2630
|
-
});
|
|
2631
|
-
};
|
|
2632
|
-
const addMultiple = (items) => {
|
|
2633
|
-
set((state) => {
|
|
2634
|
-
const newMap = new Map(state.textures);
|
|
2635
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2636
|
-
for (const [key, value] of entries) {
|
|
2637
|
-
newMap.set(key, value);
|
|
2638
|
-
}
|
|
2639
|
-
return { textures: newMap };
|
|
2640
|
-
});
|
|
2641
|
-
};
|
|
2642
|
-
const remove = (key) => {
|
|
2643
|
-
set((state) => {
|
|
2644
|
-
const newMap = new Map(state.textures);
|
|
2645
|
-
newMap.delete(key);
|
|
2646
|
-
return { textures: newMap };
|
|
2647
|
-
});
|
|
2648
|
-
};
|
|
2649
|
-
const removeMultiple = (keys) => {
|
|
2650
|
-
set((state) => {
|
|
2651
|
-
const newMap = new Map(state.textures);
|
|
2652
|
-
for (const key of keys) newMap.delete(key);
|
|
2653
|
-
return { textures: newMap };
|
|
2654
|
-
});
|
|
2655
|
-
};
|
|
2656
|
-
const dispose = (key) => {
|
|
2657
|
-
const entry = getState().textures.get(key);
|
|
2658
|
-
if (entry) {
|
|
2659
|
-
const tex = getTextureValue(entry);
|
|
2660
|
-
tex?.dispose();
|
|
2661
|
-
}
|
|
2662
|
-
remove(key);
|
|
2663
|
-
};
|
|
2664
|
-
const disposeMultiple = (keys) => {
|
|
2665
|
-
const textures = getState().textures;
|
|
2666
|
-
for (const key of keys) {
|
|
2667
|
-
const entry = textures.get(key);
|
|
2668
|
-
if (entry) {
|
|
2669
|
-
const tex = getTextureValue(entry);
|
|
2670
|
-
tex?.dispose();
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
removeMultiple(keys);
|
|
2674
|
-
};
|
|
2675
|
-
const disposeAll = () => {
|
|
2676
|
-
const textures = getState().textures;
|
|
2677
|
-
for (const entry of textures.values()) {
|
|
2678
|
-
const tex = getTextureValue(entry);
|
|
2679
|
-
tex?.dispose();
|
|
2680
|
-
}
|
|
2681
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2682
|
-
};
|
|
2264
|
+
const setState = store.setState;
|
|
2265
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2683
2266
|
return {
|
|
2684
|
-
|
|
2685
|
-
get textures() {
|
|
2267
|
+
get all() {
|
|
2686
2268
|
return getState().textures;
|
|
2687
2269
|
},
|
|
2688
|
-
|
|
2689
|
-
|
|
2270
|
+
get(input) {
|
|
2271
|
+
if (typeof input === "string") return getOne(input);
|
|
2272
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2273
|
+
const out = {};
|
|
2274
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2275
|
+
return out;
|
|
2276
|
+
},
|
|
2690
2277
|
has: (key) => getState().textures.has(key),
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2278
|
+
add(keyOrRecord, texture) {
|
|
2279
|
+
setState((state) => {
|
|
2280
|
+
const textures = new Map(state.textures);
|
|
2281
|
+
if (typeof keyOrRecord === "string") {
|
|
2282
|
+
textures.set(keyOrRecord, texture);
|
|
2283
|
+
} else {
|
|
2284
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2285
|
+
}
|
|
2286
|
+
return { textures };
|
|
2287
|
+
});
|
|
2288
|
+
},
|
|
2289
|
+
dispose(key, options) {
|
|
2290
|
+
const state = getState();
|
|
2291
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2292
|
+
if (refs > 0 && !options?.force) {
|
|
2293
|
+
console.warn(
|
|
2294
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2295
|
+
);
|
|
2296
|
+
return false;
|
|
2297
|
+
}
|
|
2298
|
+
state.textures.get(key)?.dispose();
|
|
2299
|
+
setState((s) => {
|
|
2300
|
+
const textures = new Map(s.textures);
|
|
2301
|
+
textures.delete(key);
|
|
2302
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2303
|
+
nextRefs.delete(key);
|
|
2304
|
+
return { textures, _textureRefs: nextRefs };
|
|
2305
|
+
});
|
|
2306
|
+
return true;
|
|
2307
|
+
},
|
|
2308
|
+
disposeAll() {
|
|
2309
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2310
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2311
|
+
}
|
|
2701
2312
|
};
|
|
2702
2313
|
}, [store]);
|
|
2314
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2315
|
+
const selected = useThree(subscribe);
|
|
2316
|
+
return selector ? selected : registry;
|
|
2703
2317
|
}
|
|
2704
2318
|
|
|
2705
|
-
function useRenderTarget(
|
|
2319
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2706
2320
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2707
2321
|
const size = useThree((s) => s.size);
|
|
2322
|
+
let width;
|
|
2323
|
+
let height;
|
|
2324
|
+
let opts;
|
|
2325
|
+
if (typeof widthOrOptions === "object") {
|
|
2326
|
+
opts = widthOrOptions;
|
|
2327
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2328
|
+
width = widthOrOptions;
|
|
2329
|
+
if (typeof heightOrOptions === "object") {
|
|
2330
|
+
height = widthOrOptions;
|
|
2331
|
+
opts = heightOrOptions;
|
|
2332
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2333
|
+
height = heightOrOptions;
|
|
2334
|
+
opts = options;
|
|
2335
|
+
} else {
|
|
2336
|
+
height = widthOrOptions;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2708
2339
|
return React.useMemo(() => {
|
|
2709
2340
|
const w = width ?? size.width;
|
|
2710
2341
|
const h = height ?? size.height;
|
|
2711
2342
|
{
|
|
2712
|
-
return isLegacy ? new three.WebGLRenderTarget(w, h,
|
|
2343
|
+
return isLegacy ? new three.WebGLRenderTarget(w, h, opts) : new webgpu.RenderTarget(w, h, opts);
|
|
2713
2344
|
}
|
|
2714
|
-
}, [width, height, size.width, size.height,
|
|
2345
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2715
2346
|
}
|
|
2716
2347
|
|
|
2717
2348
|
function useStore() {
|
|
@@ -2739,7 +2370,7 @@ function addEffect(callback) {
|
|
|
2739
2370
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2740
2371
|
});
|
|
2741
2372
|
const id = `legacy_effect_${effectId++}`;
|
|
2742
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2373
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2743
2374
|
}
|
|
2744
2375
|
function addAfterEffect(callback) {
|
|
2745
2376
|
notifyDepreciated({
|
|
@@ -2748,7 +2379,7 @@ function addAfterEffect(callback) {
|
|
|
2748
2379
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2749
2380
|
});
|
|
2750
2381
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2751
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2382
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2752
2383
|
}
|
|
2753
2384
|
function addTail(callback) {
|
|
2754
2385
|
notifyDepreciated({
|
|
@@ -2756,33 +2387,23 @@ function addTail(callback) {
|
|
|
2756
2387
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2757
2388
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2758
2389
|
});
|
|
2759
|
-
return getScheduler().onIdle(callback);
|
|
2390
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2760
2391
|
}
|
|
2761
2392
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2762
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2393
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2763
2394
|
}
|
|
2764
|
-
function advance(timestamp
|
|
2765
|
-
getScheduler().step(timestamp);
|
|
2395
|
+
function advance(timestamp) {
|
|
2396
|
+
scheduler.getScheduler().step(timestamp);
|
|
2766
2397
|
}
|
|
2767
2398
|
|
|
2768
|
-
const version = "10.0.0-alpha.
|
|
2399
|
+
const version = "10.0.0-alpha.2";
|
|
2769
2400
|
const packageData = {
|
|
2770
2401
|
version: version};
|
|
2771
2402
|
|
|
2772
2403
|
function Xb(Tt) {
|
|
2773
2404
|
return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
|
|
2774
2405
|
}
|
|
2775
|
-
var Rm = { exports: {} }, Og = { exports: {} };
|
|
2776
|
-
/**
|
|
2777
|
-
* @license React
|
|
2778
|
-
* react-reconciler.production.js
|
|
2779
|
-
*
|
|
2780
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2781
|
-
*
|
|
2782
|
-
* This source code is licensed under the MIT license found in the
|
|
2783
|
-
* LICENSE file in the root directory of this source tree.
|
|
2784
|
-
*/
|
|
2785
|
-
var _b;
|
|
2406
|
+
var Rm = { exports: {} }, Og = { exports: {} }, _b;
|
|
2786
2407
|
function Kb() {
|
|
2787
2408
|
return _b || (_b = 1, (function(Tt) {
|
|
2788
2409
|
Tt.exports = function(m) {
|
|
@@ -3854,7 +3475,6 @@ Error generating stack: ` + l.message + `
|
|
|
3854
3475
|
if (J === cl || J === jc) throw J;
|
|
3855
3476
|
var Ge = Yn(29, J, null, P.mode);
|
|
3856
3477
|
return Ge.lanes = H, Ge.return = P, Ge;
|
|
3857
|
-
} finally {
|
|
3858
3478
|
}
|
|
3859
3479
|
};
|
|
3860
3480
|
}
|
|
@@ -4508,7 +4128,6 @@ Error generating stack: ` + l.message + `
|
|
|
4508
4128
|
var h = r.lastRenderedState, y = d(h, a);
|
|
4509
4129
|
if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
|
|
4510
4130
|
} catch {
|
|
4511
|
-
} finally {
|
|
4512
4131
|
}
|
|
4513
4132
|
if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
|
|
4514
4133
|
}
|
|
@@ -6929,10 +6548,7 @@ Error generating stack: ` + l.message + `
|
|
|
6929
6548
|
function vr(t, r) {
|
|
6930
6549
|
Sf(t, r), (t = t.alternate) && Sf(t, r);
|
|
6931
6550
|
}
|
|
6932
|
-
var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
|
|
6933
|
-
var gc = Symbol.for("react.activity");
|
|
6934
|
-
var $r = Symbol.for("react.memo_cache_sentinel");
|
|
6935
|
-
var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6551
|
+
var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6936
6552
|
m.cloneMutableInstance;
|
|
6937
6553
|
var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
|
|
6938
6554
|
m.cloneMutableTextInstance;
|
|
@@ -7301,17 +6917,7 @@ No matching component was found for:
|
|
|
7301
6917
|
}, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
|
|
7302
6918
|
})(Og)), Og.exports;
|
|
7303
6919
|
}
|
|
7304
|
-
var Mg = { exports: {} };
|
|
7305
|
-
/**
|
|
7306
|
-
* @license React
|
|
7307
|
-
* react-reconciler.development.js
|
|
7308
|
-
*
|
|
7309
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
7310
|
-
*
|
|
7311
|
-
* This source code is licensed under the MIT license found in the
|
|
7312
|
-
* LICENSE file in the root directory of this source tree.
|
|
7313
|
-
*/
|
|
7314
|
-
var Rb;
|
|
6920
|
+
var Mg = { exports: {} }, Rb;
|
|
7315
6921
|
function e0() {
|
|
7316
6922
|
return Rb || (Rb = 1, (function(Tt) {
|
|
7317
6923
|
process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
|
|
@@ -13078,10 +12684,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
|
|
|
13078
12684
|
function Ic() {
|
|
13079
12685
|
return di;
|
|
13080
12686
|
}
|
|
13081
|
-
var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
|
|
13082
|
-
var Ds = Symbol.for("react.activity");
|
|
13083
|
-
var Bh = Symbol.for("react.memo_cache_sentinel");
|
|
13084
|
-
var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
12687
|
+
var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
13085
12688
|
m.cloneMutableInstance;
|
|
13086
12689
|
var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
|
|
13087
12690
|
m.cloneMutableTextInstance;
|
|
@@ -14049,15 +13652,6 @@ function n0() {
|
|
|
14049
13652
|
var t0 = n0();
|
|
14050
13653
|
const r0 = Xb(t0);
|
|
14051
13654
|
|
|
14052
|
-
/**
|
|
14053
|
-
* @license React
|
|
14054
|
-
* react-reconciler-constants.production.js
|
|
14055
|
-
*
|
|
14056
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14057
|
-
*
|
|
14058
|
-
* This source code is licensed under the MIT license found in the
|
|
14059
|
-
* LICENSE file in the root directory of this source tree.
|
|
14060
|
-
*/
|
|
14061
13655
|
const t = 1, o = 8, r = 32, e = 2;
|
|
14062
13656
|
|
|
14063
13657
|
function createReconciler(config) {
|
|
@@ -14084,10 +13678,11 @@ function extend(objects) {
|
|
|
14084
13678
|
function validateInstance(type, props) {
|
|
14085
13679
|
const name = toPascalCase(type);
|
|
14086
13680
|
const target = catalogue[name];
|
|
14087
|
-
if (type !== "primitive" && !target)
|
|
13681
|
+
if (type !== "primitive" && !target) {
|
|
14088
13682
|
throw new Error(
|
|
14089
13683
|
`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
|
|
14090
13684
|
);
|
|
13685
|
+
}
|
|
14091
13686
|
if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
|
|
14092
13687
|
if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
|
|
14093
13688
|
}
|
|
@@ -14251,6 +13846,7 @@ function swapInstances() {
|
|
|
14251
13846
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14252
13847
|
instance.object.__r3f = instance;
|
|
14253
13848
|
setFiberRef(fiber, instance.object);
|
|
13849
|
+
delete instance.appliedOnce;
|
|
14254
13850
|
applyProps(instance.object, instance.props);
|
|
14255
13851
|
if (instance.props.attach) {
|
|
14256
13852
|
attach(parent, instance);
|
|
@@ -14324,8 +13920,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14324
13920
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14325
13921
|
if (isTailSibling) swapInstances();
|
|
14326
13922
|
},
|
|
14327
|
-
finalizeInitialChildren: () =>
|
|
14328
|
-
|
|
13923
|
+
finalizeInitialChildren: (instance) => {
|
|
13924
|
+
for (const prop in instance.props) {
|
|
13925
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13926
|
+
}
|
|
13927
|
+
return false;
|
|
13928
|
+
},
|
|
13929
|
+
commitMount(instance) {
|
|
13930
|
+
const resolved = {};
|
|
13931
|
+
for (const prop in instance.props) {
|
|
13932
|
+
const value = instance.props[prop];
|
|
13933
|
+
if (isFromRef(value)) {
|
|
13934
|
+
const ref = value[FROM_REF];
|
|
13935
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13936
|
+
}
|
|
13937
|
+
}
|
|
13938
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14329
13939
|
},
|
|
14330
13940
|
getPublicInstance: (instance) => instance?.object,
|
|
14331
13941
|
prepareForCommit: () => null,
|
|
@@ -14538,14 +14148,17 @@ function createRoot(canvas) {
|
|
|
14538
14148
|
if (!prevRoot) _roots.set(canvas, { fiber, store });
|
|
14539
14149
|
let onCreated;
|
|
14540
14150
|
let lastCamera;
|
|
14541
|
-
|
|
14151
|
+
const lastConfiguredProps = {};
|
|
14542
14152
|
let configured = false;
|
|
14543
14153
|
let pending = null;
|
|
14544
14154
|
return {
|
|
14545
14155
|
async configure(props = {}) {
|
|
14546
14156
|
let resolve;
|
|
14547
14157
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14548
|
-
|
|
14158
|
+
const {
|
|
14159
|
+
id: canvasId,
|
|
14160
|
+
primaryCanvas,
|
|
14161
|
+
scheduler: schedulerConfig,
|
|
14549
14162
|
gl: glConfig,
|
|
14550
14163
|
renderer: rendererConfig,
|
|
14551
14164
|
size: propsSize,
|
|
@@ -14553,10 +14166,6 @@ function createRoot(canvas) {
|
|
|
14553
14166
|
events,
|
|
14554
14167
|
onCreated: onCreatedCallback,
|
|
14555
14168
|
shadows = false,
|
|
14556
|
-
linear = false,
|
|
14557
|
-
flat = false,
|
|
14558
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14559
|
-
legacy = false,
|
|
14560
14169
|
orthographic = false,
|
|
14561
14170
|
frameloop = "always",
|
|
14562
14171
|
dpr = [1, 2],
|
|
@@ -14567,9 +14176,12 @@ function createRoot(canvas) {
|
|
|
14567
14176
|
onDragOverMissed,
|
|
14568
14177
|
onDropMissed,
|
|
14569
14178
|
autoUpdateFrustum = true,
|
|
14570
|
-
occlusion = false
|
|
14179
|
+
occlusion = false,
|
|
14180
|
+
_sizeProps,
|
|
14181
|
+
forceEven
|
|
14571
14182
|
} = props;
|
|
14572
|
-
|
|
14183
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14184
|
+
const state = store.getState();
|
|
14573
14185
|
const defaultGLProps = {
|
|
14574
14186
|
canvas,
|
|
14575
14187
|
powerPreference: "high-performance",
|
|
@@ -14577,7 +14189,8 @@ function createRoot(canvas) {
|
|
|
14577
14189
|
alpha: true
|
|
14578
14190
|
};
|
|
14579
14191
|
const defaultGPUProps = {
|
|
14580
|
-
canvas
|
|
14192
|
+
canvas,
|
|
14193
|
+
antialias: true
|
|
14581
14194
|
};
|
|
14582
14195
|
const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU || !rendererConfig);
|
|
14583
14196
|
if (glConfig && rendererConfig) {
|
|
@@ -14591,26 +14204,70 @@ function createRoot(canvas) {
|
|
|
14591
14204
|
});
|
|
14592
14205
|
}
|
|
14593
14206
|
let renderer = state.internal.actualRenderer;
|
|
14207
|
+
if (primaryCanvas && wantsGL) {
|
|
14208
|
+
throw new Error(
|
|
14209
|
+
"The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
|
|
14210
|
+
);
|
|
14211
|
+
}
|
|
14594
14212
|
if (wantsGL && !state.internal.actualRenderer) {
|
|
14595
14213
|
renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
|
|
14596
14214
|
state.internal.actualRenderer = renderer;
|
|
14597
|
-
state.set({ isLegacy: true, gl: renderer, renderer });
|
|
14215
|
+
state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
|
|
14216
|
+
} else if (!wantsGL && primaryCanvas && !state.internal.actualRenderer) {
|
|
14217
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14218
|
+
renderer = primary.renderer;
|
|
14219
|
+
state.internal.actualRenderer = renderer;
|
|
14220
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14221
|
+
primary.store.setState((prev) => ({
|
|
14222
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14223
|
+
}));
|
|
14224
|
+
state.set((prev) => ({
|
|
14225
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14226
|
+
renderer,
|
|
14227
|
+
primaryStore: primary.store,
|
|
14228
|
+
internal: {
|
|
14229
|
+
...prev.internal,
|
|
14230
|
+
canvasTarget,
|
|
14231
|
+
isMultiCanvas: true,
|
|
14232
|
+
isSecondary: true,
|
|
14233
|
+
targetId: primaryCanvas
|
|
14234
|
+
}
|
|
14235
|
+
}));
|
|
14598
14236
|
} else if (!wantsGL && !state.internal.actualRenderer) {
|
|
14599
14237
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14600
14238
|
if (!renderer.hasInitialized?.()) {
|
|
14239
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14240
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14241
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14242
|
+
canvas.width = size2.width * pixelRatio;
|
|
14243
|
+
canvas.height = size2.height * pixelRatio;
|
|
14244
|
+
}
|
|
14601
14245
|
await renderer.init();
|
|
14602
14246
|
}
|
|
14603
14247
|
const backend = renderer.backend;
|
|
14604
14248
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14605
14249
|
state.internal.actualRenderer = renderer;
|
|
14606
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14250
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14251
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14252
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14253
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14254
|
+
state.set((prev) => ({
|
|
14255
|
+
internal: {
|
|
14256
|
+
...prev.internal,
|
|
14257
|
+
canvasTarget,
|
|
14258
|
+
unregisterPrimary
|
|
14259
|
+
}
|
|
14260
|
+
}));
|
|
14261
|
+
}
|
|
14607
14262
|
}
|
|
14608
14263
|
let raycaster = state.raycaster;
|
|
14609
14264
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
14610
14265
|
const { params, ...options } = raycastOptions || {};
|
|
14611
14266
|
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
|
|
14612
|
-
if (!is.equ(params, raycaster.params, shallowLoose))
|
|
14267
|
+
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14613
14268
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14269
|
+
}
|
|
14270
|
+
let tempCamera = state.camera;
|
|
14614
14271
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14615
14272
|
lastCamera = cameraOptions;
|
|
14616
14273
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14630,6 +14287,7 @@ function createRoot(canvas) {
|
|
|
14630
14287
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14631
14288
|
}
|
|
14632
14289
|
state.set({ camera });
|
|
14290
|
+
tempCamera = camera;
|
|
14633
14291
|
raycaster.camera = camera;
|
|
14634
14292
|
}
|
|
14635
14293
|
if (!state.scene) {
|
|
@@ -14647,7 +14305,7 @@ function createRoot(canvas) {
|
|
|
14647
14305
|
rootScene: scene,
|
|
14648
14306
|
internal: { ...prev.internal, container: scene }
|
|
14649
14307
|
}));
|
|
14650
|
-
const camera =
|
|
14308
|
+
const camera = tempCamera;
|
|
14651
14309
|
if (camera && !camera.parent) scene.add(camera);
|
|
14652
14310
|
}
|
|
14653
14311
|
if (events && !state.events.handlers) {
|
|
@@ -14661,9 +14319,17 @@ function createRoot(canvas) {
|
|
|
14661
14319
|
wasEnabled = enabled;
|
|
14662
14320
|
});
|
|
14663
14321
|
}
|
|
14322
|
+
if (_sizeProps !== void 0) {
|
|
14323
|
+
state.set({ _sizeProps });
|
|
14324
|
+
}
|
|
14325
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14326
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14327
|
+
}
|
|
14664
14328
|
const size = computeInitialSize(canvas, propsSize);
|
|
14665
|
-
if (!is.equ(size, state.size, shallowLoose)) {
|
|
14329
|
+
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14330
|
+
const wasImperative = state._sizeImperative;
|
|
14666
14331
|
state.setSize(size.width, size.height, size.top, size.left);
|
|
14332
|
+
if (!wasImperative) state.set({ _sizeImperative: false });
|
|
14667
14333
|
}
|
|
14668
14334
|
if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
|
|
14669
14335
|
state.setDpr(dpr);
|
|
@@ -14685,10 +14351,10 @@ function createRoot(canvas) {
|
|
|
14685
14351
|
lastConfiguredProps.performance = performance;
|
|
14686
14352
|
}
|
|
14687
14353
|
if (!state.xr) {
|
|
14688
|
-
const handleXRFrame = (timestamp,
|
|
14354
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14689
14355
|
const state2 = store.getState();
|
|
14690
14356
|
if (state2.frameloop === "never") return;
|
|
14691
|
-
advance(timestamp
|
|
14357
|
+
advance(timestamp);
|
|
14692
14358
|
};
|
|
14693
14359
|
const actualRenderer = state.internal.actualRenderer;
|
|
14694
14360
|
const handleSessionChange = () => {
|
|
@@ -14700,16 +14366,16 @@ function createRoot(canvas) {
|
|
|
14700
14366
|
};
|
|
14701
14367
|
const xr = {
|
|
14702
14368
|
connect() {
|
|
14703
|
-
const { gl, renderer: renderer2
|
|
14704
|
-
const
|
|
14705
|
-
|
|
14706
|
-
|
|
14369
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14370
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14371
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14372
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14707
14373
|
},
|
|
14708
14374
|
disconnect() {
|
|
14709
|
-
const { gl, renderer: renderer2
|
|
14710
|
-
const
|
|
14711
|
-
|
|
14712
|
-
|
|
14375
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14376
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14377
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14378
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14713
14379
|
}
|
|
14714
14380
|
};
|
|
14715
14381
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14721,70 +14387,93 @@ function createRoot(canvas) {
|
|
|
14721
14387
|
const oldType = renderer.shadowMap.type;
|
|
14722
14388
|
renderer.shadowMap.enabled = !!shadows;
|
|
14723
14389
|
if (is.boo(shadows)) {
|
|
14724
|
-
renderer.shadowMap.type = webgpu.
|
|
14390
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14725
14391
|
} else if (is.str(shadows)) {
|
|
14392
|
+
if (shadows === "soft") {
|
|
14393
|
+
notifyDepreciated({
|
|
14394
|
+
heading: 'shadows="soft" is deprecated',
|
|
14395
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14396
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14397
|
+
});
|
|
14398
|
+
}
|
|
14726
14399
|
const types = {
|
|
14727
14400
|
basic: webgpu.BasicShadowMap,
|
|
14728
14401
|
percentage: webgpu.PCFShadowMap,
|
|
14729
|
-
soft: webgpu.
|
|
14402
|
+
soft: webgpu.PCFShadowMap,
|
|
14730
14403
|
variance: webgpu.VSMShadowMap
|
|
14731
14404
|
};
|
|
14732
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14405
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14733
14406
|
} else if (is.obj(shadows)) {
|
|
14734
14407
|
Object.assign(renderer.shadowMap, shadows);
|
|
14735
14408
|
}
|
|
14736
|
-
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
|
|
14409
|
+
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
|
|
14737
14410
|
renderer.shadowMap.needsUpdate = true;
|
|
14738
|
-
}
|
|
14739
|
-
{
|
|
14740
|
-
const legacyChanged = legacy !== lastConfiguredProps.legacy;
|
|
14741
|
-
const linearChanged = linear !== lastConfiguredProps.linear;
|
|
14742
|
-
const flatChanged = flat !== lastConfiguredProps.flat;
|
|
14743
|
-
if (legacyChanged) {
|
|
14744
|
-
if (legacy)
|
|
14745
|
-
notifyDepreciated({
|
|
14746
|
-
heading: "Legacy Color Management",
|
|
14747
|
-
body: "Legacy color management is deprecated and will be removed in a future version.",
|
|
14748
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
14749
|
-
});
|
|
14750
|
-
}
|
|
14751
|
-
if (legacyChanged) {
|
|
14752
|
-
webgpu.ColorManagement.enabled = !legacy;
|
|
14753
|
-
lastConfiguredProps.legacy = legacy;
|
|
14754
|
-
}
|
|
14755
|
-
if (!configured || linearChanged) {
|
|
14756
|
-
renderer.outputColorSpace = linear ? webgpu.LinearSRGBColorSpace : webgpu.SRGBColorSpace;
|
|
14757
|
-
lastConfiguredProps.linear = linear;
|
|
14758
14411
|
}
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
|
|
14764
|
-
if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
|
|
14765
|
-
if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
|
|
14412
|
+
}
|
|
14413
|
+
if (!configured) {
|
|
14414
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14415
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14766
14416
|
}
|
|
14767
14417
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14768
14418
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14769
14419
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14770
14420
|
}
|
|
14771
|
-
|
|
14772
|
-
|
|
14421
|
+
const r3fProps = ["textureColorSpace"];
|
|
14422
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14423
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14424
|
+
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14425
|
+
const glProps = {};
|
|
14426
|
+
for (const key in glConfig) {
|
|
14427
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14428
|
+
}
|
|
14429
|
+
applyProps(renderer, glProps);
|
|
14430
|
+
}
|
|
14773
14431
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14774
14432
|
const currentRenderer = state.renderer;
|
|
14775
14433
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14776
|
-
|
|
14434
|
+
const rendererProps = {};
|
|
14435
|
+
for (const key in rendererConfig) {
|
|
14436
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14437
|
+
}
|
|
14438
|
+
applyProps(currentRenderer, rendererProps);
|
|
14777
14439
|
}
|
|
14778
14440
|
}
|
|
14779
|
-
const scheduler = getScheduler();
|
|
14441
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14780
14442
|
const rootId = state.internal.rootId;
|
|
14781
14443
|
if (!rootId) {
|
|
14782
|
-
const newRootId = scheduler.generateRootId();
|
|
14783
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14444
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14445
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14784
14446
|
getState: () => store.getState(),
|
|
14785
14447
|
onError: (err) => store.getState().setError(err)
|
|
14786
14448
|
});
|
|
14787
|
-
const
|
|
14449
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14450
|
+
() => {
|
|
14451
|
+
const state2 = store.getState();
|
|
14452
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14453
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14454
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14455
|
+
}
|
|
14456
|
+
},
|
|
14457
|
+
{
|
|
14458
|
+
id: `${newRootId}_canvasTarget`,
|
|
14459
|
+
rootId: newRootId,
|
|
14460
|
+
phase: "start",
|
|
14461
|
+
system: true
|
|
14462
|
+
}
|
|
14463
|
+
);
|
|
14464
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14465
|
+
() => {
|
|
14466
|
+
const state2 = store.getState();
|
|
14467
|
+
state2.events.flush?.();
|
|
14468
|
+
},
|
|
14469
|
+
{
|
|
14470
|
+
id: `${newRootId}_events`,
|
|
14471
|
+
rootId: newRootId,
|
|
14472
|
+
phase: "input",
|
|
14473
|
+
system: true
|
|
14474
|
+
}
|
|
14475
|
+
);
|
|
14476
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14788
14477
|
() => {
|
|
14789
14478
|
const state2 = store.getState();
|
|
14790
14479
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14794,11 +14483,11 @@ function createRoot(canvas) {
|
|
|
14794
14483
|
{
|
|
14795
14484
|
id: `${newRootId}_frustum`,
|
|
14796
14485
|
rootId: newRootId,
|
|
14797
|
-
|
|
14486
|
+
before: "render",
|
|
14798
14487
|
system: true
|
|
14799
14488
|
}
|
|
14800
14489
|
);
|
|
14801
|
-
const unregisterVisibility = scheduler.register(
|
|
14490
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14802
14491
|
() => {
|
|
14803
14492
|
const state2 = store.getState();
|
|
14804
14493
|
checkVisibility(state2);
|
|
@@ -14806,30 +14495,34 @@ function createRoot(canvas) {
|
|
|
14806
14495
|
{
|
|
14807
14496
|
id: `${newRootId}_visibility`,
|
|
14808
14497
|
rootId: newRootId,
|
|
14809
|
-
|
|
14498
|
+
before: "render",
|
|
14810
14499
|
system: true,
|
|
14811
14500
|
after: `${newRootId}_frustum`
|
|
14812
14501
|
}
|
|
14813
14502
|
);
|
|
14814
|
-
const unregisterRender = scheduler.register(
|
|
14503
|
+
const unregisterRender = scheduler$1.register(
|
|
14815
14504
|
() => {
|
|
14816
14505
|
const state2 = store.getState();
|
|
14817
14506
|
const renderer2 = state2.internal.actualRenderer;
|
|
14818
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14507
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14819
14508
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14820
14509
|
try {
|
|
14821
|
-
if (state2.
|
|
14510
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14822
14511
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14823
14512
|
} catch (error) {
|
|
14824
14513
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14825
14514
|
}
|
|
14826
14515
|
},
|
|
14827
14516
|
{
|
|
14828
|
-
|
|
14517
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14518
|
+
id: canvasId || `${newRootId}_render`,
|
|
14829
14519
|
rootId: newRootId,
|
|
14830
14520
|
phase: "render",
|
|
14831
|
-
system: true
|
|
14521
|
+
system: true,
|
|
14832
14522
|
// Internal flag: this is a system job, not user-controlled
|
|
14523
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14524
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14525
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14833
14526
|
}
|
|
14834
14527
|
);
|
|
14835
14528
|
state.set((state2) => ({
|
|
@@ -14838,15 +14531,17 @@ function createRoot(canvas) {
|
|
|
14838
14531
|
rootId: newRootId,
|
|
14839
14532
|
unregisterRoot: () => {
|
|
14840
14533
|
unregisterRoot();
|
|
14534
|
+
unregisterCanvasTarget();
|
|
14535
|
+
unregisterEventsFlush();
|
|
14841
14536
|
unregisterFrustum();
|
|
14842
14537
|
unregisterVisibility();
|
|
14843
14538
|
unregisterRender();
|
|
14844
14539
|
},
|
|
14845
|
-
scheduler
|
|
14540
|
+
scheduler: scheduler$1
|
|
14846
14541
|
}
|
|
14847
14542
|
}));
|
|
14848
14543
|
}
|
|
14849
|
-
scheduler.frameloop = frameloop;
|
|
14544
|
+
scheduler$1.frameloop = frameloop;
|
|
14850
14545
|
onCreated = onCreatedCallback;
|
|
14851
14546
|
configured = true;
|
|
14852
14547
|
resolve();
|
|
@@ -14896,15 +14591,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14896
14591
|
const renderer = state.internal.actualRenderer;
|
|
14897
14592
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14898
14593
|
if (unregisterRoot) unregisterRoot();
|
|
14594
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14595
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14596
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14597
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14899
14598
|
state.events.disconnect?.();
|
|
14900
14599
|
cleanupHelperGroup(root.store);
|
|
14901
|
-
renderer
|
|
14902
|
-
|
|
14903
|
-
|
|
14600
|
+
if (state.isLegacy && renderer) {
|
|
14601
|
+
;
|
|
14602
|
+
renderer.renderLists?.dispose?.();
|
|
14603
|
+
renderer.forceContextLoss?.();
|
|
14604
|
+
}
|
|
14605
|
+
if (!state.internal.isSecondary) {
|
|
14606
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14607
|
+
}
|
|
14904
14608
|
dispose(state.scene);
|
|
14905
14609
|
_roots.delete(canvas);
|
|
14906
14610
|
if (callback) callback(canvas);
|
|
14907
|
-
} catch
|
|
14611
|
+
} catch {
|
|
14908
14612
|
}
|
|
14909
14613
|
}, 500);
|
|
14910
14614
|
}
|
|
@@ -14912,36 +14616,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14912
14616
|
}
|
|
14913
14617
|
}
|
|
14914
14618
|
function createPortal(children, container, state) {
|
|
14915
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14619
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14916
14620
|
}
|
|
14917
|
-
function
|
|
14621
|
+
function Portal({ children, container, state }) {
|
|
14918
14622
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14919
|
-
const [resolvedContainer,
|
|
14623
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14920
14624
|
if (isRef(container)) return container.current ?? null;
|
|
14921
14625
|
return container;
|
|
14922
14626
|
});
|
|
14627
|
+
const setResolvedContainer = React.useCallback(
|
|
14628
|
+
(newContainer) => {
|
|
14629
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14630
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14631
|
+
},
|
|
14632
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14633
|
+
);
|
|
14923
14634
|
React.useMemo(() => {
|
|
14924
|
-
if (isRef(container)) {
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
const updated = container.current;
|
|
14929
|
-
if (updated && updated !== resolvedContainer) {
|
|
14930
|
-
setResolvedContainer(updated);
|
|
14931
|
-
}
|
|
14932
|
-
});
|
|
14933
|
-
} else if (current !== resolvedContainer) {
|
|
14934
|
-
setResolvedContainer(current);
|
|
14935
|
-
}
|
|
14936
|
-
} else if (container !== resolvedContainer) {
|
|
14937
|
-
setResolvedContainer(container);
|
|
14635
|
+
if (isRef(container) && !container.current) {
|
|
14636
|
+
return queueMicrotask(() => {
|
|
14637
|
+
setResolvedContainer(container.current);
|
|
14638
|
+
});
|
|
14938
14639
|
}
|
|
14939
|
-
|
|
14640
|
+
setResolvedContainer(container);
|
|
14641
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14940
14642
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14941
14643
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14942
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14644
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14943
14645
|
}
|
|
14944
|
-
function
|
|
14646
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14945
14647
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14946
14648
|
const previousRoot = useStore();
|
|
14947
14649
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14962,11 +14664,12 @@ function Portal({ state = {}, children, container }) {
|
|
|
14962
14664
|
};
|
|
14963
14665
|
}, [portalScene, container, injectScene]);
|
|
14964
14666
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14667
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14965
14668
|
let viewport = void 0;
|
|
14966
|
-
if (injectState.camera && size) {
|
|
14669
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14967
14670
|
const camera = injectState.camera;
|
|
14968
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14969
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14671
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14672
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14970
14673
|
}
|
|
14971
14674
|
return {
|
|
14972
14675
|
// The intersect consists of the previous root state
|
|
@@ -14983,7 +14686,7 @@ function Portal({ state = {}, children, container }) {
|
|
|
14983
14686
|
previousRoot,
|
|
14984
14687
|
// Events, size and viewport can be overridden by the inject layer
|
|
14985
14688
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14986
|
-
size:
|
|
14689
|
+
size: resolvedSize,
|
|
14987
14690
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14988
14691
|
// Layers are allowed to override events
|
|
14989
14692
|
setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
@@ -14995,9 +14698,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14995
14698
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14996
14699
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14997
14700
|
onMutate(previousRoot.getState());
|
|
14998
|
-
previousRoot.subscribe(onMutate);
|
|
14999
14701
|
return store;
|
|
15000
14702
|
}, [previousRoot, container]);
|
|
14703
|
+
useIsomorphicLayoutEffect(() => {
|
|
14704
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14705
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14706
|
+
return unsubscribe;
|
|
14707
|
+
}, [previousRoot, usePortalStore]);
|
|
15001
14708
|
return (
|
|
15002
14709
|
// @ts-ignore, reconciler types are not maintained
|
|
15003
14710
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -15011,21 +14718,59 @@ function flushSync(fn) {
|
|
|
15011
14718
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15012
14719
|
}
|
|
15013
14720
|
|
|
14721
|
+
function parseBackground(background) {
|
|
14722
|
+
if (!background) return null;
|
|
14723
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14724
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14725
|
+
return {
|
|
14726
|
+
...rest,
|
|
14727
|
+
preset,
|
|
14728
|
+
files: envMap || files,
|
|
14729
|
+
backgroundFiles: backgroundMap,
|
|
14730
|
+
background: true
|
|
14731
|
+
};
|
|
14732
|
+
}
|
|
14733
|
+
if (typeof background === "number") {
|
|
14734
|
+
return { color: background, background: true };
|
|
14735
|
+
}
|
|
14736
|
+
if (typeof background === "string") {
|
|
14737
|
+
if (background in presetsObj) {
|
|
14738
|
+
return { preset: background, background: true };
|
|
14739
|
+
}
|
|
14740
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14741
|
+
return { files: background, background: true };
|
|
14742
|
+
}
|
|
14743
|
+
return { color: background, background: true };
|
|
14744
|
+
}
|
|
14745
|
+
if (background.isColor) {
|
|
14746
|
+
return { color: background, background: true };
|
|
14747
|
+
}
|
|
14748
|
+
return null;
|
|
14749
|
+
}
|
|
14750
|
+
|
|
14751
|
+
function clearHmrCaches(store) {
|
|
14752
|
+
store.setState((state) => ({
|
|
14753
|
+
nodes: {},
|
|
14754
|
+
uniforms: {},
|
|
14755
|
+
buffers: {},
|
|
14756
|
+
gpuStorage: {},
|
|
14757
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14758
|
+
}));
|
|
14759
|
+
}
|
|
14760
|
+
|
|
15014
14761
|
function CanvasImpl({
|
|
15015
14762
|
ref,
|
|
15016
14763
|
children,
|
|
15017
14764
|
fallback,
|
|
15018
14765
|
resize,
|
|
15019
14766
|
style,
|
|
14767
|
+
id,
|
|
15020
14768
|
gl,
|
|
15021
|
-
renderer,
|
|
14769
|
+
renderer: rendererProp,
|
|
15022
14770
|
events = createPointerEvents,
|
|
15023
14771
|
eventSource,
|
|
15024
14772
|
eventPrefix,
|
|
15025
14773
|
shadows,
|
|
15026
|
-
linear,
|
|
15027
|
-
flat,
|
|
15028
|
-
legacy,
|
|
15029
14774
|
orthographic,
|
|
15030
14775
|
frameloop,
|
|
15031
14776
|
dpr,
|
|
@@ -15033,14 +14778,34 @@ function CanvasImpl({
|
|
|
15033
14778
|
raycaster,
|
|
15034
14779
|
camera,
|
|
15035
14780
|
scene,
|
|
14781
|
+
autoUpdateFrustum,
|
|
14782
|
+
occlusion,
|
|
15036
14783
|
onPointerMissed,
|
|
15037
14784
|
onDragOverMissed,
|
|
15038
14785
|
onDropMissed,
|
|
15039
14786
|
onCreated,
|
|
14787
|
+
hmr,
|
|
14788
|
+
width,
|
|
14789
|
+
height,
|
|
14790
|
+
background,
|
|
14791
|
+
forceEven,
|
|
15040
14792
|
...props
|
|
15041
14793
|
}) {
|
|
14794
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14795
|
+
let primaryCanvas;
|
|
14796
|
+
let scheduler;
|
|
14797
|
+
let renderer;
|
|
14798
|
+
if (isRendererConfig) {
|
|
14799
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14800
|
+
primaryCanvas = pc;
|
|
14801
|
+
scheduler = sc;
|
|
14802
|
+
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
14803
|
+
} else {
|
|
14804
|
+
renderer = rendererProp;
|
|
14805
|
+
}
|
|
15042
14806
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15043
14807
|
const Bridge = useBridge();
|
|
14808
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15044
14809
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15045
14810
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15046
14811
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15057,7 +14822,21 @@ function CanvasImpl({
|
|
|
15057
14822
|
};
|
|
15058
14823
|
}, [resize, hasInitialSizeRef.current]);
|
|
15059
14824
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15060
|
-
|
|
14825
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14826
|
+
let w = width ?? containerRect.width;
|
|
14827
|
+
let h = height ?? containerRect.height;
|
|
14828
|
+
if (forceEven) {
|
|
14829
|
+
w = Math.ceil(w / 2) * 2;
|
|
14830
|
+
h = Math.ceil(h / 2) * 2;
|
|
14831
|
+
}
|
|
14832
|
+
return {
|
|
14833
|
+
width: w,
|
|
14834
|
+
height: h,
|
|
14835
|
+
top: containerRect.top,
|
|
14836
|
+
left: containerRect.left
|
|
14837
|
+
};
|
|
14838
|
+
}, [width, height, containerRect, forceEven]);
|
|
14839
|
+
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15061
14840
|
hasInitialSizeRef.current = true;
|
|
15062
14841
|
}
|
|
15063
14842
|
const canvasRef = React__namespace.useRef(null);
|
|
@@ -15076,7 +14855,7 @@ function CanvasImpl({
|
|
|
15076
14855
|
useIsomorphicLayoutEffect(() => {
|
|
15077
14856
|
effectActiveRef.current = true;
|
|
15078
14857
|
const canvas = canvasRef.current;
|
|
15079
|
-
if (
|
|
14858
|
+
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15080
14859
|
if (!root.current) {
|
|
15081
14860
|
root.current = createRoot(canvas);
|
|
15082
14861
|
notifyAlpha({
|
|
@@ -15096,21 +14875,26 @@ function CanvasImpl({
|
|
|
15096
14875
|
async function run() {
|
|
15097
14876
|
if (!effectActiveRef.current || !root.current) return;
|
|
15098
14877
|
await root.current.configure({
|
|
14878
|
+
id,
|
|
14879
|
+
primaryCanvas,
|
|
14880
|
+
scheduler,
|
|
15099
14881
|
gl,
|
|
15100
14882
|
renderer,
|
|
15101
14883
|
scene,
|
|
15102
14884
|
events,
|
|
15103
14885
|
shadows,
|
|
15104
|
-
linear,
|
|
15105
|
-
flat,
|
|
15106
|
-
legacy,
|
|
15107
14886
|
orthographic,
|
|
15108
14887
|
frameloop,
|
|
15109
14888
|
dpr,
|
|
15110
14889
|
performance,
|
|
15111
14890
|
raycaster,
|
|
15112
14891
|
camera,
|
|
15113
|
-
|
|
14892
|
+
autoUpdateFrustum,
|
|
14893
|
+
occlusion,
|
|
14894
|
+
size: effectiveSize,
|
|
14895
|
+
// Store size props for reset functionality
|
|
14896
|
+
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14897
|
+
forceEven,
|
|
15114
14898
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15115
14899
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15116
14900
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15134,7 +14918,10 @@ function CanvasImpl({
|
|
|
15134
14918
|
});
|
|
15135
14919
|
if (!effectActiveRef.current || !root.current) return;
|
|
15136
14920
|
root.current.render(
|
|
15137
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14921
|
+
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
|
|
14922
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
14923
|
+
children ?? null
|
|
14924
|
+
] }) }) })
|
|
15138
14925
|
);
|
|
15139
14926
|
}
|
|
15140
14927
|
run();
|
|
@@ -15156,6 +14943,28 @@ function CanvasImpl({
|
|
|
15156
14943
|
};
|
|
15157
14944
|
}
|
|
15158
14945
|
}, []);
|
|
14946
|
+
React__namespace.useEffect(() => {
|
|
14947
|
+
if (hmr === false) return;
|
|
14948
|
+
const canvas = canvasRef.current;
|
|
14949
|
+
if (!canvas) return;
|
|
14950
|
+
const handleHMR = () => {
|
|
14951
|
+
queueMicrotask(() => {
|
|
14952
|
+
const rootEntry = _roots.get(canvas);
|
|
14953
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
14954
|
+
});
|
|
14955
|
+
};
|
|
14956
|
+
if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
|
|
14957
|
+
const hot = undefined;
|
|
14958
|
+
hot.on("vite:afterUpdate", handleHMR);
|
|
14959
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
14960
|
+
}
|
|
14961
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
14962
|
+
const hot = module.hot;
|
|
14963
|
+
hot.addStatusHandler((status) => {
|
|
14964
|
+
if (status === "idle") handleHMR();
|
|
14965
|
+
});
|
|
14966
|
+
}
|
|
14967
|
+
}, [hmr]);
|
|
15159
14968
|
const pointerEvents = eventSource ? "none" : "auto";
|
|
15160
14969
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15161
14970
|
"div",
|
|
@@ -15170,7 +14979,16 @@ function CanvasImpl({
|
|
|
15170
14979
|
...style
|
|
15171
14980
|
},
|
|
15172
14981
|
...props,
|
|
15173
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14982
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14983
|
+
"canvas",
|
|
14984
|
+
{
|
|
14985
|
+
ref: canvasRef,
|
|
14986
|
+
id,
|
|
14987
|
+
className: "r3f-canvas",
|
|
14988
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
14989
|
+
children: fallback
|
|
14990
|
+
}
|
|
14991
|
+
) })
|
|
15174
14992
|
}
|
|
15175
14993
|
);
|
|
15176
14994
|
}
|
|
@@ -15180,15 +14998,23 @@ function Canvas(props) {
|
|
|
15180
14998
|
|
|
15181
14999
|
extend(THREE);
|
|
15182
15000
|
|
|
15001
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
15002
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15183
15003
|
exports.Block = Block;
|
|
15184
15004
|
exports.Canvas = Canvas;
|
|
15005
|
+
exports.Environment = Environment;
|
|
15006
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
15007
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
15008
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15185
15009
|
exports.ErrorBoundary = ErrorBoundary;
|
|
15010
|
+
exports.FROM_REF = FROM_REF;
|
|
15186
15011
|
exports.IsObject = IsObject;
|
|
15012
|
+
exports.ONCE = ONCE;
|
|
15013
|
+
exports.Portal = Portal;
|
|
15187
15014
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15188
15015
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15189
15016
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15190
15017
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15191
|
-
exports.Scheduler = Scheduler;
|
|
15192
15018
|
exports.Texture = Texture;
|
|
15193
15019
|
exports._roots = _roots;
|
|
15194
15020
|
exports.act = act;
|
|
@@ -15213,30 +15039,40 @@ exports.events = createPointerEvents;
|
|
|
15213
15039
|
exports.extend = extend;
|
|
15214
15040
|
exports.findInitialRoot = findInitialRoot;
|
|
15215
15041
|
exports.flushSync = flushSync;
|
|
15042
|
+
exports.fromRef = fromRef;
|
|
15216
15043
|
exports.getInstanceProps = getInstanceProps;
|
|
15044
|
+
exports.getPrimary = getPrimary;
|
|
15045
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15217
15046
|
exports.getRootState = getRootState;
|
|
15218
|
-
exports.getScheduler = getScheduler;
|
|
15219
15047
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15220
15048
|
exports.hasConstructor = hasConstructor;
|
|
15049
|
+
exports.hasPrimary = hasPrimary;
|
|
15221
15050
|
exports.invalidate = invalidate;
|
|
15222
15051
|
exports.invalidateInstance = invalidateInstance;
|
|
15223
15052
|
exports.is = is;
|
|
15224
15053
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15225
15054
|
exports.isCopyable = isCopyable;
|
|
15055
|
+
exports.isFromRef = isFromRef;
|
|
15226
15056
|
exports.isObject3D = isObject3D;
|
|
15057
|
+
exports.isOnce = isOnce;
|
|
15227
15058
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15228
15059
|
exports.isRef = isRef;
|
|
15229
15060
|
exports.isRenderer = isRenderer;
|
|
15230
15061
|
exports.isTexture = isTexture;
|
|
15231
15062
|
exports.isVectorLike = isVectorLike;
|
|
15063
|
+
exports.once = once;
|
|
15232
15064
|
exports.prepare = prepare;
|
|
15065
|
+
exports.presetsObj = presetsObj;
|
|
15233
15066
|
exports.reconciler = reconciler;
|
|
15067
|
+
exports.registerPrimary = registerPrimary;
|
|
15234
15068
|
exports.removeInteractivity = removeInteractivity;
|
|
15235
15069
|
exports.resolve = resolve;
|
|
15236
15070
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
15071
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15237
15072
|
exports.updateCamera = updateCamera;
|
|
15238
15073
|
exports.updateFrustum = updateFrustum;
|
|
15239
15074
|
exports.useBridge = useBridge;
|
|
15075
|
+
exports.useEnvironment = useEnvironment;
|
|
15240
15076
|
exports.useFrame = useFrame;
|
|
15241
15077
|
exports.useGraph = useGraph;
|
|
15242
15078
|
exports.useInstanceHandle = useInstanceHandle;
|
|
@@ -15248,3 +15084,4 @@ exports.useStore = useStore;
|
|
|
15248
15084
|
exports.useTexture = useTexture;
|
|
15249
15085
|
exports.useTextures = useTextures;
|
|
15250
15086
|
exports.useThree = useThree;
|
|
15087
|
+
exports.waitForPrimary = waitForPrimary;
|