@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.0706a92
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 +1274 -1405
- package/dist/index.d.cts +1888 -1713
- package/dist/index.d.mts +1888 -1713
- package/dist/index.d.ts +1888 -1713
- package/dist/index.mjs +1235 -1384
- package/dist/legacy.cjs +1231 -1386
- package/dist/legacy.d.cts +1889 -1714
- package/dist/legacy.d.mts +1889 -1714
- package/dist/legacy.d.ts +1889 -1714
- package/dist/legacy.mjs +1192 -1365
- package/dist/webgpu/index.cjs +1832 -1516
- package/dist/webgpu/index.d.cts +2082 -1737
- package/dist/webgpu/index.d.mts +2082 -1737
- package/dist/webgpu/index.d.ts +2082 -1737
- package/dist/webgpu/index.mjs +1790 -1496
- package/package.json +4 -1
- package/readme.md +244 -318
package/dist/webgpu/index.cjs
CHANGED
|
@@ -6,8 +6,15 @@ const jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
const React = require('react');
|
|
7
7
|
const useMeasure = require('react-use-measure');
|
|
8
8
|
const itsFine = require('its-fine');
|
|
9
|
+
const fiber = require('@react-three/fiber');
|
|
10
|
+
const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
|
|
11
|
+
const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
|
|
12
|
+
const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
|
|
13
|
+
const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
|
|
14
|
+
const gainmapJs = require('@monogrid/gainmap-js');
|
|
9
15
|
const Tb = require('scheduler');
|
|
10
16
|
const traditional = require('zustand/traditional');
|
|
17
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
11
18
|
const suspendReact = require('suspend-react');
|
|
12
19
|
const lite = require('dequal/lite');
|
|
13
20
|
require('zustand/shallow');
|
|
@@ -67,9 +74,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
67
74
|
WebGLRenderer: WebGLRenderer
|
|
68
75
|
}, [webgpu__namespace]);
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
78
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
79
|
+
function registerPrimary(id, renderer, store) {
|
|
80
|
+
if (primaryRegistry.has(id)) {
|
|
81
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
82
|
+
}
|
|
83
|
+
const entry = { renderer, store };
|
|
84
|
+
primaryRegistry.set(id, entry);
|
|
85
|
+
const subscribers = pendingSubscribers.get(id);
|
|
86
|
+
if (subscribers) {
|
|
87
|
+
subscribers.forEach((callback) => callback(entry));
|
|
88
|
+
pendingSubscribers.delete(id);
|
|
89
|
+
}
|
|
90
|
+
return () => {
|
|
91
|
+
const currentEntry = primaryRegistry.get(id);
|
|
92
|
+
if (currentEntry?.renderer === renderer) {
|
|
93
|
+
primaryRegistry.delete(id);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function getPrimary(id) {
|
|
98
|
+
return primaryRegistry.get(id);
|
|
99
|
+
}
|
|
100
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
101
|
+
const existing = primaryRegistry.get(id);
|
|
102
|
+
if (existing) {
|
|
103
|
+
return Promise.resolve(existing);
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const timeoutId = setTimeout(() => {
|
|
107
|
+
const subscribers = pendingSubscribers.get(id);
|
|
108
|
+
if (subscribers) {
|
|
109
|
+
const index = subscribers.indexOf(callback);
|
|
110
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
111
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
112
|
+
}
|
|
113
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
114
|
+
}, timeout);
|
|
115
|
+
const callback = (entry) => {
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
resolve(entry);
|
|
118
|
+
};
|
|
119
|
+
if (!pendingSubscribers.has(id)) {
|
|
120
|
+
pendingSubscribers.set(id, []);
|
|
121
|
+
}
|
|
122
|
+
pendingSubscribers.get(id).push(callback);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function hasPrimary(id) {
|
|
126
|
+
return primaryRegistry.has(id);
|
|
127
|
+
}
|
|
128
|
+
function unregisterPrimary(id) {
|
|
129
|
+
primaryRegistry.delete(id);
|
|
130
|
+
}
|
|
131
|
+
function getPrimaryIds() {
|
|
132
|
+
return Array.from(primaryRegistry.keys());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const presetsObj = {
|
|
136
|
+
apartment: "lebombo_1k.hdr",
|
|
137
|
+
city: "potsdamer_platz_1k.hdr",
|
|
138
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
139
|
+
forest: "forest_slope_1k.hdr",
|
|
140
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
141
|
+
night: "dikhololo_night_1k.hdr",
|
|
142
|
+
park: "rooitou_park_1k.hdr",
|
|
143
|
+
studio: "studio_small_03_1k.hdr",
|
|
144
|
+
sunset: "venice_sunset_1k.hdr",
|
|
145
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
149
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
150
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
151
|
+
function useEnvironment({
|
|
152
|
+
files = defaultFiles,
|
|
153
|
+
path = "",
|
|
154
|
+
preset = void 0,
|
|
155
|
+
colorSpace = void 0,
|
|
156
|
+
extensions
|
|
157
|
+
} = {}) {
|
|
158
|
+
if (preset) {
|
|
159
|
+
validatePreset(preset);
|
|
160
|
+
files = presetsObj[preset];
|
|
161
|
+
path = CUBEMAP_ROOT;
|
|
162
|
+
}
|
|
163
|
+
const multiFile = isArray(files);
|
|
164
|
+
const { extension, isCubemap } = getExtension(files);
|
|
165
|
+
const loader = getLoader$1(extension);
|
|
166
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
167
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
168
|
+
React.useLayoutEffect(() => {
|
|
169
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
170
|
+
function clearGainmapTexture() {
|
|
171
|
+
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
172
|
+
}
|
|
173
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
174
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
175
|
+
const loaderResult = fiber.useLoader(
|
|
176
|
+
loader,
|
|
177
|
+
multiFile ? [files] : files,
|
|
178
|
+
(loader2) => {
|
|
179
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
180
|
+
loader2.setRenderer?.(renderer);
|
|
181
|
+
}
|
|
182
|
+
loader2.setPath?.(path);
|
|
183
|
+
if (extensions) extensions(loader2);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
let texture = multiFile ? (
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
loaderResult[0]
|
|
189
|
+
) : loaderResult;
|
|
190
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
191
|
+
texture = texture.renderTarget?.texture;
|
|
192
|
+
}
|
|
193
|
+
texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
|
|
194
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
195
|
+
return texture;
|
|
196
|
+
}
|
|
197
|
+
const preloadDefaultOptions = {
|
|
198
|
+
files: defaultFiles,
|
|
199
|
+
path: "",
|
|
200
|
+
preset: void 0,
|
|
201
|
+
extensions: void 0
|
|
202
|
+
};
|
|
203
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
204
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
205
|
+
let { files, path = "" } = options;
|
|
206
|
+
const { preset, extensions } = options;
|
|
207
|
+
if (preset) {
|
|
208
|
+
validatePreset(preset);
|
|
209
|
+
files = presetsObj[preset];
|
|
210
|
+
path = CUBEMAP_ROOT;
|
|
211
|
+
}
|
|
212
|
+
const { extension } = getExtension(files);
|
|
213
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
214
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
215
|
+
}
|
|
216
|
+
const loader = getLoader$1(extension);
|
|
217
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
218
|
+
fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
219
|
+
loader2.setPath?.(path);
|
|
220
|
+
if (extensions) extensions(loader2);
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
const clearDefaultOptins = {
|
|
224
|
+
files: defaultFiles,
|
|
225
|
+
preset: void 0
|
|
226
|
+
};
|
|
227
|
+
useEnvironment.clear = (clearOptions) => {
|
|
228
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
229
|
+
let { files } = options;
|
|
230
|
+
const { preset } = options;
|
|
231
|
+
if (preset) {
|
|
232
|
+
validatePreset(preset);
|
|
233
|
+
files = presetsObj[preset];
|
|
234
|
+
}
|
|
235
|
+
const { extension } = getExtension(files);
|
|
236
|
+
const loader = getLoader$1(extension);
|
|
237
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
238
|
+
fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
|
|
239
|
+
};
|
|
240
|
+
function validatePreset(preset) {
|
|
241
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
242
|
+
}
|
|
243
|
+
function getExtension(files) {
|
|
244
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
245
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
246
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
247
|
+
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();
|
|
248
|
+
return { extension, isCubemap, isGainmap };
|
|
249
|
+
}
|
|
250
|
+
function getLoader$1(extension) {
|
|
251
|
+
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;
|
|
252
|
+
return loader;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
256
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
257
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
258
|
+
sceneProps = {
|
|
259
|
+
backgroundBlurriness: 0,
|
|
260
|
+
backgroundIntensity: 1,
|
|
261
|
+
backgroundRotation: [0, 0, 0],
|
|
262
|
+
environmentIntensity: 1,
|
|
263
|
+
environmentRotation: [0, 0, 0],
|
|
264
|
+
...sceneProps
|
|
265
|
+
};
|
|
266
|
+
const target = resolveScene(scene || defaultScene);
|
|
267
|
+
const oldbg = target.background;
|
|
268
|
+
const oldenv = target.environment;
|
|
269
|
+
const oldSceneProps = {
|
|
270
|
+
// @ts-ignore
|
|
271
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
272
|
+
// @ts-ignore
|
|
273
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
276
|
+
// @ts-ignore
|
|
277
|
+
environmentIntensity: target.environmentIntensity,
|
|
278
|
+
// @ts-ignore
|
|
279
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
280
|
+
};
|
|
281
|
+
if (background !== "only") target.environment = texture;
|
|
282
|
+
if (background) target.background = texture;
|
|
283
|
+
fiber.applyProps(target, sceneProps);
|
|
284
|
+
return () => {
|
|
285
|
+
if (background !== "only") target.environment = oldenv;
|
|
286
|
+
if (background) target.background = oldbg;
|
|
287
|
+
fiber.applyProps(target, oldSceneProps);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
291
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
292
|
+
React__namespace.useLayoutEffect(() => {
|
|
293
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
294
|
+
});
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
function EnvironmentCube({
|
|
298
|
+
background = false,
|
|
299
|
+
scene,
|
|
300
|
+
blur,
|
|
301
|
+
backgroundBlurriness,
|
|
302
|
+
backgroundIntensity,
|
|
303
|
+
backgroundRotation,
|
|
304
|
+
environmentIntensity,
|
|
305
|
+
environmentRotation,
|
|
306
|
+
...rest
|
|
307
|
+
}) {
|
|
308
|
+
const texture = useEnvironment(rest);
|
|
309
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
310
|
+
React__namespace.useLayoutEffect(() => {
|
|
311
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
312
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
313
|
+
backgroundIntensity,
|
|
314
|
+
backgroundRotation,
|
|
315
|
+
environmentIntensity,
|
|
316
|
+
environmentRotation
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
React__namespace.useEffect(() => {
|
|
320
|
+
return () => {
|
|
321
|
+
texture.dispose();
|
|
322
|
+
};
|
|
323
|
+
}, [texture]);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
function EnvironmentPortal({
|
|
327
|
+
children,
|
|
328
|
+
near = 0.1,
|
|
329
|
+
far = 1e3,
|
|
330
|
+
resolution = 256,
|
|
331
|
+
frames = 1,
|
|
332
|
+
map,
|
|
333
|
+
background = false,
|
|
334
|
+
blur,
|
|
335
|
+
backgroundBlurriness,
|
|
336
|
+
backgroundIntensity,
|
|
337
|
+
backgroundRotation,
|
|
338
|
+
environmentIntensity,
|
|
339
|
+
environmentRotation,
|
|
340
|
+
scene,
|
|
341
|
+
files,
|
|
342
|
+
path,
|
|
343
|
+
preset = void 0,
|
|
344
|
+
extensions
|
|
345
|
+
}) {
|
|
346
|
+
const gl = fiber.useThree((state) => state.gl);
|
|
347
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
348
|
+
const camera = React__namespace.useRef(null);
|
|
349
|
+
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
350
|
+
const fbo = React__namespace.useMemo(() => {
|
|
351
|
+
const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
|
|
352
|
+
fbo2.texture.type = webgpu.HalfFloatType;
|
|
353
|
+
return fbo2;
|
|
354
|
+
}, [resolution]);
|
|
355
|
+
React__namespace.useEffect(() => {
|
|
356
|
+
return () => {
|
|
357
|
+
fbo.dispose();
|
|
358
|
+
};
|
|
359
|
+
}, [fbo]);
|
|
360
|
+
React__namespace.useLayoutEffect(() => {
|
|
361
|
+
if (frames === 1) {
|
|
362
|
+
const autoClear = gl.autoClear;
|
|
363
|
+
gl.autoClear = true;
|
|
364
|
+
camera.current.update(gl, virtualScene);
|
|
365
|
+
gl.autoClear = autoClear;
|
|
366
|
+
}
|
|
367
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
368
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
369
|
+
backgroundIntensity,
|
|
370
|
+
backgroundRotation,
|
|
371
|
+
environmentIntensity,
|
|
372
|
+
environmentRotation
|
|
373
|
+
});
|
|
374
|
+
}, [
|
|
375
|
+
children,
|
|
376
|
+
virtualScene,
|
|
377
|
+
fbo.texture,
|
|
378
|
+
scene,
|
|
379
|
+
defaultScene,
|
|
380
|
+
background,
|
|
381
|
+
frames,
|
|
382
|
+
gl,
|
|
383
|
+
blur,
|
|
384
|
+
backgroundBlurriness,
|
|
385
|
+
backgroundIntensity,
|
|
386
|
+
backgroundRotation,
|
|
387
|
+
environmentIntensity,
|
|
388
|
+
environmentRotation
|
|
389
|
+
]);
|
|
390
|
+
let count = 1;
|
|
391
|
+
fiber.useFrame(() => {
|
|
392
|
+
if (frames === Infinity || count < frames) {
|
|
393
|
+
const autoClear = gl.autoClear;
|
|
394
|
+
gl.autoClear = true;
|
|
395
|
+
camera.current.update(gl, virtualScene);
|
|
396
|
+
gl.autoClear = autoClear;
|
|
397
|
+
count++;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
|
|
401
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
402
|
+
children,
|
|
403
|
+
/* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
404
|
+
files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
405
|
+
] }),
|
|
406
|
+
virtualScene
|
|
407
|
+
) });
|
|
408
|
+
}
|
|
409
|
+
function EnvironmentGround(props) {
|
|
410
|
+
const textureDefault = useEnvironment(props);
|
|
411
|
+
const texture = props.map || textureDefault;
|
|
412
|
+
React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
|
|
413
|
+
React__namespace.useEffect(() => {
|
|
414
|
+
return () => {
|
|
415
|
+
textureDefault.dispose();
|
|
416
|
+
};
|
|
417
|
+
}, [textureDefault]);
|
|
418
|
+
const height = props.ground?.height ?? 15;
|
|
419
|
+
const radius = props.ground?.radius ?? 60;
|
|
420
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
421
|
+
const args = React__namespace.useMemo(
|
|
422
|
+
() => [texture, height, radius],
|
|
423
|
+
[texture, height, radius]
|
|
424
|
+
);
|
|
425
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
|
|
428
|
+
] });
|
|
429
|
+
}
|
|
430
|
+
function EnvironmentColor({ color, scene }) {
|
|
431
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
432
|
+
React__namespace.useLayoutEffect(() => {
|
|
433
|
+
if (color === void 0) return;
|
|
434
|
+
const target = resolveScene(scene || defaultScene);
|
|
435
|
+
const oldBg = target.background;
|
|
436
|
+
target.background = new webgpu.Color(color);
|
|
437
|
+
return () => {
|
|
438
|
+
target.background = oldBg;
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
function EnvironmentDualSource(props) {
|
|
444
|
+
const { backgroundFiles, ...envProps } = props;
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
446
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
447
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
448
|
+
] });
|
|
449
|
+
}
|
|
450
|
+
function Environment(props) {
|
|
451
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
452
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
|
|
453
|
+
}
|
|
454
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
455
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
|
|
456
|
+
}
|
|
457
|
+
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 });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
var __defProp$1 = Object.defineProperty;
|
|
461
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
462
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
73
463
|
const act = React__namespace["act"];
|
|
74
464
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
75
465
|
function useMutableCallback(fn) {
|
|
@@ -101,7 +491,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
101
491
|
return _a = class extends React__namespace.Component {
|
|
102
492
|
constructor() {
|
|
103
493
|
super(...arguments);
|
|
104
|
-
__publicField$
|
|
494
|
+
__publicField$1(this, "state", { error: false });
|
|
105
495
|
}
|
|
106
496
|
componentDidCatch(err) {
|
|
107
497
|
this.props.set(err);
|
|
@@ -109,7 +499,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
109
499
|
render() {
|
|
110
500
|
return this.state.error ? null : this.props.children;
|
|
111
501
|
}
|
|
112
|
-
}, __publicField$
|
|
502
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
113
503
|
})();
|
|
114
504
|
|
|
115
505
|
const is = {
|
|
@@ -295,7 +685,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
295
685
|
let occlusionSetupPromise = null;
|
|
296
686
|
function enableOcclusion(store) {
|
|
297
687
|
const state = store.getState();
|
|
298
|
-
const { internal, renderer
|
|
688
|
+
const { internal, renderer } = state;
|
|
299
689
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
300
690
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
301
691
|
if (!hasOcclusionSupport) {
|
|
@@ -403,6 +793,17 @@ function unregisterVisibility(store, object) {
|
|
|
403
793
|
internal.visibilityRegistry.delete(object.uuid);
|
|
404
794
|
internal.occlusionCache.delete(object);
|
|
405
795
|
}
|
|
796
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
797
|
+
const { internal } = store.getState();
|
|
798
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
799
|
+
if (!entry) return false;
|
|
800
|
+
entry.handlers = handlers;
|
|
801
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
802
|
+
object.occlusionTest = true;
|
|
803
|
+
enableOcclusion(store);
|
|
804
|
+
}
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
406
807
|
function checkVisibility(state) {
|
|
407
808
|
const { internal, camera } = state;
|
|
408
809
|
const registry = internal.visibilityRegistry;
|
|
@@ -458,6 +859,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
458
859
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
459
860
|
}
|
|
460
861
|
|
|
862
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
863
|
+
function fromRef(ref) {
|
|
864
|
+
return { [FROM_REF]: ref };
|
|
865
|
+
}
|
|
866
|
+
function isFromRef(value) {
|
|
867
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
871
|
+
function once(...args) {
|
|
872
|
+
return { [ONCE]: args.length ? args : true };
|
|
873
|
+
}
|
|
874
|
+
function isOnce(value) {
|
|
875
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
876
|
+
}
|
|
877
|
+
|
|
461
878
|
const RESERVED_PROPS = [
|
|
462
879
|
"children",
|
|
463
880
|
"key",
|
|
@@ -528,7 +945,7 @@ function getMemoizedPrototype(root) {
|
|
|
528
945
|
ctor = new root.constructor();
|
|
529
946
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
530
947
|
}
|
|
531
|
-
} catch
|
|
948
|
+
} catch {
|
|
532
949
|
}
|
|
533
950
|
return ctor;
|
|
534
951
|
}
|
|
@@ -574,6 +991,22 @@ function applyProps(object, props) {
|
|
|
574
991
|
continue;
|
|
575
992
|
}
|
|
576
993
|
if (value === void 0) continue;
|
|
994
|
+
if (isFromRef(value)) continue;
|
|
995
|
+
if (isOnce(value)) {
|
|
996
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
997
|
+
if (instance) {
|
|
998
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
999
|
+
instance.appliedOnce.add(prop);
|
|
1000
|
+
}
|
|
1001
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
1002
|
+
const args = value[ONCE];
|
|
1003
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
1004
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
1005
|
+
} else if (args !== true && args.length > 0) {
|
|
1006
|
+
targetRoot[targetKey] = args[0];
|
|
1007
|
+
}
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
577
1010
|
let { root, key, target } = resolve(object, prop);
|
|
578
1011
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
579
1012
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -594,9 +1027,25 @@ function applyProps(object, props) {
|
|
|
594
1027
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
595
1028
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
596
1029
|
else target.set(value);
|
|
1030
|
+
} else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1031
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1032
|
+
const uniforms = root.uniforms;
|
|
1033
|
+
const nextUniforms = value;
|
|
1034
|
+
for (const name in nextUniforms) {
|
|
1035
|
+
const uniform = nextUniforms[name];
|
|
1036
|
+
const targetUniform = uniforms[name];
|
|
1037
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1038
|
+
else {
|
|
1039
|
+
const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
|
|
1040
|
+
uniforms[name] = nextUniform;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
597
1043
|
} else {
|
|
598
1044
|
root[key] = value;
|
|
599
|
-
if (
|
|
1045
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1046
|
+
root.needsUpdate = true;
|
|
1047
|
+
}
|
|
1048
|
+
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
|
|
600
1049
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
601
1050
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
602
1051
|
}
|
|
@@ -609,6 +1058,9 @@ function applyProps(object, props) {
|
|
|
609
1058
|
if (instance.eventCount && object2.raycast !== null) {
|
|
610
1059
|
rootState.internal.interaction.push(object2);
|
|
611
1060
|
}
|
|
1061
|
+
}
|
|
1062
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1063
|
+
const object2 = instance.object;
|
|
612
1064
|
const root = findInitialRoot(instance);
|
|
613
1065
|
const visibilityHandlers = {
|
|
614
1066
|
onFramed: instance.handlers.onFramed,
|
|
@@ -616,7 +1068,9 @@ function applyProps(object, props) {
|
|
|
616
1068
|
onVisible: instance.handlers.onVisible
|
|
617
1069
|
};
|
|
618
1070
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
619
|
-
|
|
1071
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1072
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1073
|
+
}
|
|
620
1074
|
} else {
|
|
621
1075
|
unregisterVisibility(root, object2);
|
|
622
1076
|
}
|
|
@@ -629,38 +1083,60 @@ function applyProps(object, props) {
|
|
|
629
1083
|
return object;
|
|
630
1084
|
}
|
|
631
1085
|
|
|
1086
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1087
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1088
|
+
function getPointerState(internal, pointerId) {
|
|
1089
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1090
|
+
if (!state) {
|
|
1091
|
+
state = {
|
|
1092
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1093
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1094
|
+
initialClick: [0, 0],
|
|
1095
|
+
initialHits: []
|
|
1096
|
+
};
|
|
1097
|
+
internal.pointerMap.set(pointerId, state);
|
|
1098
|
+
}
|
|
1099
|
+
return state;
|
|
1100
|
+
}
|
|
1101
|
+
function getPointerId(event) {
|
|
1102
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1103
|
+
}
|
|
632
1104
|
function makeId(event) {
|
|
633
1105
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
634
1106
|
}
|
|
635
|
-
function releaseInternalPointerCapture(
|
|
636
|
-
const
|
|
1107
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1108
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1109
|
+
if (!pointerState) return;
|
|
1110
|
+
const captureData = pointerState.captured.get(obj);
|
|
637
1111
|
if (captureData) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
capturedMap.delete(pointerId);
|
|
641
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
642
|
-
}
|
|
1112
|
+
pointerState.captured.delete(obj);
|
|
1113
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
643
1114
|
}
|
|
644
1115
|
}
|
|
645
1116
|
function removeInteractivity(store, object) {
|
|
646
1117
|
const { internal } = store.getState();
|
|
647
1118
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1119
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1120
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1121
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1122
|
+
if (value.eventObject === object || value.object === object) {
|
|
1123
|
+
pointerState.hovered.delete(key);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
if (pointerState.captured.has(object)) {
|
|
1127
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
652
1128
|
}
|
|
653
|
-
}
|
|
654
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
655
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
656
|
-
});
|
|
1129
|
+
}
|
|
657
1130
|
unregisterVisibility(store, object);
|
|
658
1131
|
}
|
|
659
1132
|
function createEvents(store) {
|
|
660
|
-
function calculateDistance(event) {
|
|
1133
|
+
function calculateDistance(event, pointerId) {
|
|
661
1134
|
const { internal } = store.getState();
|
|
662
|
-
const
|
|
663
|
-
|
|
1135
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1136
|
+
if (!pointerState) return 0;
|
|
1137
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1138
|
+
const dx = event.offsetX - initialX;
|
|
1139
|
+
const dy = event.offsetY - initialY;
|
|
664
1140
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
665
1141
|
}
|
|
666
1142
|
function filterPointerEvents(objects) {
|
|
@@ -696,6 +1172,15 @@ function createEvents(store) {
|
|
|
696
1172
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
697
1173
|
}
|
|
698
1174
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1175
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1176
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1177
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1178
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1179
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1180
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1181
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
699
1184
|
const aState = getRootState(a.object);
|
|
700
1185
|
const bState = getRootState(b.object);
|
|
701
1186
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -717,9 +1202,13 @@ function createEvents(store) {
|
|
|
717
1202
|
eventObject = eventObject.parent;
|
|
718
1203
|
}
|
|
719
1204
|
}
|
|
720
|
-
if ("pointerId" in event
|
|
721
|
-
|
|
722
|
-
|
|
1205
|
+
if ("pointerId" in event) {
|
|
1206
|
+
const pointerId = event.pointerId;
|
|
1207
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1208
|
+
if (pointerState?.captured.size) {
|
|
1209
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1210
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1211
|
+
}
|
|
723
1212
|
}
|
|
724
1213
|
}
|
|
725
1214
|
return intersections;
|
|
@@ -732,27 +1221,25 @@ function createEvents(store) {
|
|
|
732
1221
|
if (state) {
|
|
733
1222
|
const { raycaster, pointer, camera, internal } = state;
|
|
734
1223
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
735
|
-
const hasPointerCapture = (id) =>
|
|
1224
|
+
const hasPointerCapture = (id) => {
|
|
1225
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1226
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1227
|
+
};
|
|
736
1228
|
const setPointerCapture = (id) => {
|
|
737
1229
|
const captureData = { intersection: hit, target: event.target };
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
} else {
|
|
741
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
742
|
-
}
|
|
1230
|
+
const pointerState = getPointerState(internal, id);
|
|
1231
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
743
1232
|
event.target.setPointerCapture(id);
|
|
744
1233
|
};
|
|
745
1234
|
const releasePointerCapture = (id) => {
|
|
746
|
-
|
|
747
|
-
if (captures) {
|
|
748
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
749
|
-
}
|
|
1235
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
750
1236
|
};
|
|
751
1237
|
const extractEventProps = {};
|
|
752
1238
|
for (const prop in event) {
|
|
753
1239
|
const property = event[prop];
|
|
754
1240
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
755
1241
|
}
|
|
1242
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
756
1243
|
const raycastEvent = {
|
|
757
1244
|
...hit,
|
|
758
1245
|
...extractEventProps,
|
|
@@ -763,18 +1250,19 @@ function createEvents(store) {
|
|
|
763
1250
|
unprojectedPoint,
|
|
764
1251
|
ray: raycaster.ray,
|
|
765
1252
|
camera,
|
|
1253
|
+
pointerId: eventPointerId,
|
|
766
1254
|
// Hijack stopPropagation, which just sets a flag
|
|
767
1255
|
stopPropagation() {
|
|
768
|
-
const
|
|
1256
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
769
1257
|
if (
|
|
770
1258
|
// ...if this pointer hasn't been captured
|
|
771
|
-
!
|
|
772
|
-
|
|
1259
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1260
|
+
pointerState.captured.has(hit.eventObject)
|
|
773
1261
|
) {
|
|
774
1262
|
raycastEvent.stopped = localState.stopped = true;
|
|
775
|
-
if (
|
|
1263
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
776
1264
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
777
|
-
cancelPointer([...higher, hit]);
|
|
1265
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
778
1266
|
}
|
|
779
1267
|
}
|
|
780
1268
|
},
|
|
@@ -790,15 +1278,18 @@ function createEvents(store) {
|
|
|
790
1278
|
}
|
|
791
1279
|
return intersections;
|
|
792
1280
|
}
|
|
793
|
-
function cancelPointer(intersections) {
|
|
1281
|
+
function cancelPointer(intersections, pointerId) {
|
|
794
1282
|
const { internal } = store.getState();
|
|
795
|
-
|
|
1283
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1284
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1285
|
+
if (!pointerState) return;
|
|
1286
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
796
1287
|
if (!intersections.length || !intersections.find(
|
|
797
1288
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
798
1289
|
)) {
|
|
799
1290
|
const eventObject = hoveredObj.eventObject;
|
|
800
1291
|
const instance = eventObject.__r3f;
|
|
801
|
-
|
|
1292
|
+
pointerState.hovered.delete(hoveredId);
|
|
802
1293
|
if (instance?.eventCount) {
|
|
803
1294
|
const handlers = instance.handlers;
|
|
804
1295
|
const data = { ...hoveredObj, intersections };
|
|
@@ -827,41 +1318,118 @@ function createEvents(store) {
|
|
|
827
1318
|
instance?.handlers.onDropMissed?.(event);
|
|
828
1319
|
}
|
|
829
1320
|
}
|
|
1321
|
+
function cleanupPointer(pointerId) {
|
|
1322
|
+
const { internal } = store.getState();
|
|
1323
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1324
|
+
if (pointerState) {
|
|
1325
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1326
|
+
const eventObject = hoveredObj.eventObject;
|
|
1327
|
+
const instance = eventObject.__r3f;
|
|
1328
|
+
if (instance?.eventCount) {
|
|
1329
|
+
const handlers = instance.handlers;
|
|
1330
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1331
|
+
handlers.onPointerOut?.(data);
|
|
1332
|
+
handlers.onPointerLeave?.(data);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
internal.pointerMap.delete(pointerId);
|
|
1336
|
+
}
|
|
1337
|
+
internal.pointerDirty.delete(pointerId);
|
|
1338
|
+
}
|
|
1339
|
+
function processDeferredPointer(event, pointerId) {
|
|
1340
|
+
const state = store.getState();
|
|
1341
|
+
const { internal } = state;
|
|
1342
|
+
if (!state.events.enabled) return;
|
|
1343
|
+
const filter = filterPointerEvents;
|
|
1344
|
+
const hits = intersect(event, filter);
|
|
1345
|
+
cancelPointer(hits, pointerId);
|
|
1346
|
+
function onIntersect(data) {
|
|
1347
|
+
const eventObject = data.eventObject;
|
|
1348
|
+
const instance = eventObject.__r3f;
|
|
1349
|
+
if (!instance?.eventCount) return;
|
|
1350
|
+
const handlers = instance.handlers;
|
|
1351
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1352
|
+
const id = makeId(data);
|
|
1353
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1354
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1355
|
+
if (!hoveredItem) {
|
|
1356
|
+
pointerState.hovered.set(id, data);
|
|
1357
|
+
handlers.onPointerOver?.(data);
|
|
1358
|
+
handlers.onPointerEnter?.(data);
|
|
1359
|
+
} else if (hoveredItem.stopped) {
|
|
1360
|
+
data.stopPropagation();
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
handlers.onPointerMove?.(data);
|
|
1364
|
+
}
|
|
1365
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1366
|
+
}
|
|
830
1367
|
function handlePointer(name) {
|
|
831
1368
|
switch (name) {
|
|
832
1369
|
case "onPointerLeave":
|
|
833
|
-
case "onPointerCancel":
|
|
834
1370
|
case "onDragLeave":
|
|
835
1371
|
return () => cancelPointer([]);
|
|
1372
|
+
// Global cancel of these events
|
|
1373
|
+
case "onPointerCancel":
|
|
1374
|
+
return (event) => {
|
|
1375
|
+
const pointerId = getPointerId(event);
|
|
1376
|
+
cleanupPointer(pointerId);
|
|
1377
|
+
};
|
|
836
1378
|
case "onLostPointerCapture":
|
|
837
1379
|
return (event) => {
|
|
838
1380
|
const { internal } = store.getState();
|
|
839
|
-
|
|
1381
|
+
const pointerId = getPointerId(event);
|
|
1382
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1383
|
+
if (pointerState?.captured.size) {
|
|
840
1384
|
requestAnimationFrame(() => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1385
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1386
|
+
if (pointerState2?.captured.size) {
|
|
1387
|
+
pointerState2.captured.clear();
|
|
844
1388
|
}
|
|
1389
|
+
cancelPointer([], pointerId);
|
|
845
1390
|
});
|
|
846
1391
|
}
|
|
847
1392
|
};
|
|
848
1393
|
}
|
|
849
1394
|
return function handleEvent(event) {
|
|
850
1395
|
const state = store.getState();
|
|
851
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1396
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1397
|
+
const pointerId = getPointerId(event);
|
|
852
1398
|
internal.lastEvent.current = event;
|
|
853
|
-
if (!
|
|
1399
|
+
if (!events.enabled) return;
|
|
854
1400
|
const isPointerMove = name === "onPointerMove";
|
|
855
1401
|
const isDragOver = name === "onDragOver";
|
|
856
1402
|
const isDrop = name === "onDrop";
|
|
857
1403
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1404
|
+
const isPointerDown = name === "onPointerDown";
|
|
1405
|
+
const isPointerUp = name === "onPointerUp";
|
|
1406
|
+
const isWheel = name === "onWheel";
|
|
1407
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1408
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1409
|
+
events.compute?.(event, state);
|
|
1410
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1414
|
+
events.compute?.(event, state);
|
|
1415
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1419
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1420
|
+
internal.pointerDirty.delete(pointerId);
|
|
1421
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1422
|
+
}
|
|
858
1423
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
859
1424
|
const hits = intersect(event, filter);
|
|
860
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1425
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1426
|
+
if (isPointerDown) {
|
|
1427
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1428
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1429
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1430
|
+
}
|
|
1431
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1432
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
865
1433
|
if (isClickEvent && !hits.length) {
|
|
866
1434
|
if (delta <= 2) {
|
|
867
1435
|
pointerMissed(event, internal.interaction);
|
|
@@ -876,7 +1444,9 @@ function createEvents(store) {
|
|
|
876
1444
|
dropMissed(event, internal.interaction);
|
|
877
1445
|
if (onDropMissed) onDropMissed(event);
|
|
878
1446
|
}
|
|
879
|
-
if (isPointerMove || isDragOver)
|
|
1447
|
+
if (isPointerMove || isDragOver) {
|
|
1448
|
+
cancelPointer(hits, pointerId);
|
|
1449
|
+
}
|
|
880
1450
|
function onIntersect(data) {
|
|
881
1451
|
const eventObject = data.eventObject;
|
|
882
1452
|
const instance = eventObject.__r3f;
|
|
@@ -885,9 +1455,10 @@ function createEvents(store) {
|
|
|
885
1455
|
if (isPointerMove) {
|
|
886
1456
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
887
1457
|
const id = makeId(data);
|
|
888
|
-
const
|
|
1458
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1459
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
889
1460
|
if (!hoveredItem) {
|
|
890
|
-
|
|
1461
|
+
pointerState2.hovered.set(id, data);
|
|
891
1462
|
handlers.onPointerOver?.(data);
|
|
892
1463
|
handlers.onPointerEnter?.(data);
|
|
893
1464
|
} else if (hoveredItem.stopped) {
|
|
@@ -897,9 +1468,10 @@ function createEvents(store) {
|
|
|
897
1468
|
handlers.onPointerMove?.(data);
|
|
898
1469
|
} else if (isDragOver) {
|
|
899
1470
|
const id = makeId(data);
|
|
900
|
-
const
|
|
1471
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1472
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
901
1473
|
if (!hoveredItem) {
|
|
902
|
-
|
|
1474
|
+
pointerState2.hovered.set(id, data);
|
|
903
1475
|
handlers.onDragOverEnter?.(data);
|
|
904
1476
|
} else if (hoveredItem.stopped) {
|
|
905
1477
|
data.stopPropagation();
|
|
@@ -910,18 +1482,18 @@ function createEvents(store) {
|
|
|
910
1482
|
} else {
|
|
911
1483
|
const handler = handlers[name];
|
|
912
1484
|
if (handler) {
|
|
913
|
-
if (!isClickEvent ||
|
|
1485
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
914
1486
|
pointerMissed(
|
|
915
1487
|
event,
|
|
916
|
-
internal.interaction.filter((object) => !
|
|
1488
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
917
1489
|
);
|
|
918
1490
|
handler(data);
|
|
919
1491
|
}
|
|
920
1492
|
} else {
|
|
921
|
-
if (isClickEvent &&
|
|
1493
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
922
1494
|
pointerMissed(
|
|
923
1495
|
event,
|
|
924
|
-
internal.interaction.filter((object) => !
|
|
1496
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
925
1497
|
);
|
|
926
1498
|
}
|
|
927
1499
|
}
|
|
@@ -930,7 +1502,15 @@ function createEvents(store) {
|
|
|
930
1502
|
handleIntersects(hits, event, delta, onIntersect);
|
|
931
1503
|
};
|
|
932
1504
|
}
|
|
933
|
-
|
|
1505
|
+
function flushDeferredPointers() {
|
|
1506
|
+
const { internal, events } = store.getState();
|
|
1507
|
+
if (!events.frameTimedRaycasts) return;
|
|
1508
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1509
|
+
processDeferredPointer(event, pointerId);
|
|
1510
|
+
}
|
|
1511
|
+
internal.pointerDirty.clear();
|
|
1512
|
+
}
|
|
1513
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
934
1514
|
}
|
|
935
1515
|
const DOM_EVENTS = {
|
|
936
1516
|
onClick: ["click", false],
|
|
@@ -949,11 +1529,16 @@ const DOM_EVENTS = {
|
|
|
949
1529
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
950
1530
|
};
|
|
951
1531
|
function createPointerEvents(store) {
|
|
952
|
-
const { handlePointer } = createEvents(store);
|
|
1532
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1533
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1534
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
953
1535
|
return {
|
|
954
1536
|
priority: 1,
|
|
955
1537
|
enabled: true,
|
|
956
|
-
|
|
1538
|
+
frameTimedRaycasts: true,
|
|
1539
|
+
alwaysFireOnScroll: true,
|
|
1540
|
+
updateOnFrame: false,
|
|
1541
|
+
compute(event, state) {
|
|
957
1542
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
958
1543
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
959
1544
|
},
|
|
@@ -962,11 +1547,33 @@ function createPointerEvents(store) {
|
|
|
962
1547
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
963
1548
|
{}
|
|
964
1549
|
),
|
|
965
|
-
update: () => {
|
|
1550
|
+
update: (pointerId) => {
|
|
966
1551
|
const { events, internal } = store.getState();
|
|
967
|
-
if (
|
|
1552
|
+
if (!events.handlers) return;
|
|
1553
|
+
if (pointerId !== void 0) {
|
|
1554
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1555
|
+
if (event) {
|
|
1556
|
+
internal.pointerDirty.delete(pointerId);
|
|
1557
|
+
processDeferredPointer(event, pointerId);
|
|
1558
|
+
} else if (internal.lastEvent?.current) {
|
|
1559
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1560
|
+
}
|
|
1561
|
+
} else {
|
|
1562
|
+
flushDeferredPointers();
|
|
1563
|
+
if (internal.lastEvent?.current) {
|
|
1564
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
flush: () => {
|
|
1569
|
+
const { events, internal } = store.getState();
|
|
1570
|
+
flushDeferredPointers();
|
|
1571
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1572
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1573
|
+
}
|
|
968
1574
|
},
|
|
969
1575
|
connect: (target) => {
|
|
1576
|
+
if (!target) return;
|
|
970
1577
|
const { set, events } = store.getState();
|
|
971
1578
|
events.disconnect?.();
|
|
972
1579
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -990,6 +1597,33 @@ function createPointerEvents(store) {
|
|
|
990
1597
|
}
|
|
991
1598
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
992
1599
|
}
|
|
1600
|
+
},
|
|
1601
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1602
|
+
registerPointer: (config) => {
|
|
1603
|
+
const pointerId = nextXRPointerId++;
|
|
1604
|
+
xrPointers.set(pointerId, config);
|
|
1605
|
+
const { internal } = store.getState();
|
|
1606
|
+
getPointerState(internal, pointerId);
|
|
1607
|
+
return pointerId;
|
|
1608
|
+
},
|
|
1609
|
+
unregisterPointer: (pointerId) => {
|
|
1610
|
+
xrPointers.delete(pointerId);
|
|
1611
|
+
const { internal } = store.getState();
|
|
1612
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1613
|
+
if (pointerState) {
|
|
1614
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1615
|
+
const eventObject = hoveredObj.eventObject;
|
|
1616
|
+
const instance = eventObject.__r3f;
|
|
1617
|
+
if (instance?.eventCount) {
|
|
1618
|
+
const handlers = instance.handlers;
|
|
1619
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1620
|
+
handlers.onPointerOut?.(data);
|
|
1621
|
+
handlers.onPointerLeave?.(data);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
internal.pointerMap.delete(pointerId);
|
|
1625
|
+
}
|
|
1626
|
+
internal.pointerDirty.delete(pointerId);
|
|
993
1627
|
}
|
|
994
1628
|
};
|
|
995
1629
|
}
|
|
@@ -1051,6 +1685,14 @@ function notifyAlpha({ message, link }) {
|
|
|
1051
1685
|
}
|
|
1052
1686
|
}
|
|
1053
1687
|
|
|
1688
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1689
|
+
const INTERNAL_FIBER_MARKERS = ["packages/fiber/src", "packages/fiber/dist", "@react-three/fiber", "@react-three_fiber"];
|
|
1690
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1691
|
+
if (!stack) return false;
|
|
1692
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1693
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m)) || INTERNAL_FIBER_MARKERS.some((m) => caller.includes(m));
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1054
1696
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1055
1697
|
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
1056
1698
|
const createStore = (invalidate, advance) => {
|
|
@@ -1080,23 +1722,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1080
1722
|
set,
|
|
1081
1723
|
get,
|
|
1082
1724
|
// Mock objects that have to be configured
|
|
1725
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1726
|
+
primaryStore: null,
|
|
1083
1727
|
gl: null,
|
|
1084
1728
|
renderer: null,
|
|
1085
1729
|
camera: null,
|
|
1086
1730
|
frustum: new webgpu.Frustum(),
|
|
1087
1731
|
autoUpdateFrustum: true,
|
|
1088
1732
|
raycaster: null,
|
|
1089
|
-
events: {
|
|
1733
|
+
events: {
|
|
1734
|
+
priority: 1,
|
|
1735
|
+
enabled: true,
|
|
1736
|
+
connected: false,
|
|
1737
|
+
frameTimedRaycasts: true,
|
|
1738
|
+
alwaysFireOnScroll: true,
|
|
1739
|
+
updateOnFrame: false
|
|
1740
|
+
},
|
|
1090
1741
|
scene: null,
|
|
1091
1742
|
rootScene: null,
|
|
1092
1743
|
xr: null,
|
|
1093
1744
|
inspector: null,
|
|
1094
1745
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1095
1746
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1096
|
-
|
|
1097
|
-
linear: false,
|
|
1098
|
-
flat: false,
|
|
1099
|
-
textureColorSpace: "srgb",
|
|
1747
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1100
1748
|
isLegacy: false,
|
|
1101
1749
|
webGPUSupported: false,
|
|
1102
1750
|
isNative: false,
|
|
@@ -1154,6 +1802,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1154
1802
|
size: newSize,
|
|
1155
1803
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1156
1804
|
}));
|
|
1805
|
+
scheduler.getScheduler().invalidate();
|
|
1157
1806
|
}
|
|
1158
1807
|
}
|
|
1159
1808
|
return;
|
|
@@ -1168,6 +1817,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1168
1817
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1169
1818
|
_sizeImperative: true
|
|
1170
1819
|
}));
|
|
1820
|
+
scheduler.getScheduler().invalidate();
|
|
1171
1821
|
},
|
|
1172
1822
|
setDpr: (dpr) => set((state2) => {
|
|
1173
1823
|
const resolved = calculateDpr(dpr);
|
|
@@ -1175,14 +1825,18 @@ const createStore = (invalidate, advance) => {
|
|
|
1175
1825
|
}),
|
|
1176
1826
|
setFrameloop: (frameloop = "always") => {
|
|
1177
1827
|
set(() => ({ frameloop }));
|
|
1828
|
+
scheduler.getScheduler().frameloop = frameloop;
|
|
1178
1829
|
},
|
|
1179
1830
|
setError: (error) => set(() => ({ error })),
|
|
1180
1831
|
error: null,
|
|
1181
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1832
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1182
1833
|
uniforms: {},
|
|
1183
1834
|
nodes: {},
|
|
1835
|
+
buffers: {},
|
|
1836
|
+
gpuStorage: {},
|
|
1184
1837
|
textures: /* @__PURE__ */ new Map(),
|
|
1185
|
-
|
|
1838
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1839
|
+
renderPipeline: null,
|
|
1186
1840
|
passes: {},
|
|
1187
1841
|
_hmrVersion: 0,
|
|
1188
1842
|
_sizeImperative: false,
|
|
@@ -1191,12 +1845,16 @@ const createStore = (invalidate, advance) => {
|
|
|
1191
1845
|
internal: {
|
|
1192
1846
|
// Events
|
|
1193
1847
|
interaction: [],
|
|
1194
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1195
1848
|
subscribers: [],
|
|
1849
|
+
// Per-pointer state (new unified structure)
|
|
1850
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1851
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1852
|
+
lastEvent: React__namespace.createRef(),
|
|
1853
|
+
// Deprecated but kept for backwards compatibility
|
|
1854
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1196
1855
|
initialClick: [0, 0],
|
|
1197
1856
|
initialHits: [],
|
|
1198
1857
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1199
|
-
lastEvent: React__namespace.createRef(),
|
|
1200
1858
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1201
1859
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1202
1860
|
// Occlusion system (WebGPU only)
|
|
@@ -1235,8 +1893,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1235
1893
|
const currentState = rootStore.getState();
|
|
1236
1894
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
1237
1895
|
const stack = new Error().stack || "";
|
|
1238
|
-
|
|
1239
|
-
if (!isInternalAccess) {
|
|
1896
|
+
if (!isInternalRendererAccess(stack)) {
|
|
1240
1897
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
1241
1898
|
notifyDepreciated({
|
|
1242
1899
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -1279,13 +1936,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1279
1936
|
rootStore.subscribe(() => {
|
|
1280
1937
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1281
1938
|
const actualRenderer = internal.actualRenderer;
|
|
1939
|
+
const canvasTarget = internal.canvasTarget;
|
|
1282
1940
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1283
1941
|
oldSize = size;
|
|
1284
1942
|
oldDpr = viewport.dpr;
|
|
1285
1943
|
updateCamera(camera, size);
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1944
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1945
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1946
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1947
|
+
} else {
|
|
1948
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1949
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1950
|
+
if (canvasTarget) {
|
|
1951
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1952
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1289
1955
|
}
|
|
1290
1956
|
if (camera !== oldCamera) {
|
|
1291
1957
|
oldCamera = camera;
|
|
@@ -1340,1157 +2006,125 @@ function loadingFn(extensions, onProgress) {
|
|
|
1340
2006
|
);
|
|
1341
2007
|
};
|
|
1342
2008
|
}
|
|
1343
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
1344
|
-
const
|
|
2009
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2010
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2011
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
1345
2012
|
const fn = loadingFn(extensions, onProgress);
|
|
1346
|
-
const results =
|
|
2013
|
+
const results = cacheKeys.map(
|
|
2014
|
+
(key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2015
|
+
);
|
|
1347
2016
|
return Array.isArray(input) ? results : results[0];
|
|
1348
2017
|
}
|
|
1349
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
1350
|
-
const
|
|
1351
|
-
|
|
2018
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2019
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2020
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2021
|
+
cacheKeys.forEach(
|
|
2022
|
+
(key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2023
|
+
);
|
|
1352
2024
|
};
|
|
1353
|
-
useLoader.clear = function(loader, input) {
|
|
1354
|
-
const
|
|
1355
|
-
|
|
2025
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2026
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2027
|
+
cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
|
|
1356
2028
|
};
|
|
1357
2029
|
useLoader.loader = getLoader;
|
|
1358
2030
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
2031
|
+
function useFrame(callback, priorityOrOptions) {
|
|
2032
|
+
const store = React__namespace.useContext(context);
|
|
2033
|
+
const isInsideCanvas = store !== null;
|
|
2034
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
2035
|
+
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2036
|
+
id: priorityOrOptions.id,
|
|
2037
|
+
phase: priorityOrOptions.phase,
|
|
2038
|
+
priority: priorityOrOptions.priority,
|
|
2039
|
+
fps: priorityOrOptions.fps,
|
|
2040
|
+
drop: priorityOrOptions.drop,
|
|
2041
|
+
enabled: priorityOrOptions.enabled,
|
|
2042
|
+
before: priorityOrOptions.before,
|
|
2043
|
+
after: priorityOrOptions.after
|
|
2044
|
+
}) : "";
|
|
2045
|
+
const options = React__namespace.useMemo(() => {
|
|
2046
|
+
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2047
|
+
}, [optionsKey]);
|
|
2048
|
+
const reactId = React__namespace.useId();
|
|
2049
|
+
const id = options.id ?? reactId;
|
|
2050
|
+
const callbackRef = useMutableCallback(callback);
|
|
2051
|
+
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2052
|
+
useIsomorphicLayoutEffect(() => {
|
|
2053
|
+
if (!callback) return;
|
|
2054
|
+
if (isInsideCanvas) {
|
|
2055
|
+
const state = store.getState();
|
|
2056
|
+
const rootId = state.internal.rootId;
|
|
2057
|
+
if (isLegacyPriority) {
|
|
2058
|
+
state.internal.priority++;
|
|
2059
|
+
let parentRoot = state.previousRoot;
|
|
2060
|
+
while (parentRoot) {
|
|
2061
|
+
const parentState = parentRoot.getState();
|
|
2062
|
+
if (parentState?.internal) parentState.internal.priority++;
|
|
2063
|
+
parentRoot = parentState?.previousRoot;
|
|
2064
|
+
}
|
|
2065
|
+
notifyDepreciated({
|
|
2066
|
+
heading: "useFrame with numeric priority is deprecated",
|
|
2067
|
+
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2068
|
+
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
const wrappedCallback = (frameState, delta) => {
|
|
2072
|
+
const localState = store.getState();
|
|
2073
|
+
const mergedState = {
|
|
2074
|
+
...localState,
|
|
2075
|
+
time: frameState.time,
|
|
2076
|
+
delta: frameState.delta,
|
|
2077
|
+
elapsed: frameState.elapsed,
|
|
2078
|
+
frame: frameState.frame
|
|
2079
|
+
};
|
|
2080
|
+
callbackRef.current?.(mergedState, delta);
|
|
2081
|
+
};
|
|
2082
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2083
|
+
id,
|
|
2084
|
+
rootId,
|
|
2085
|
+
...options
|
|
2086
|
+
});
|
|
2087
|
+
return () => {
|
|
2088
|
+
unregister();
|
|
2089
|
+
if (isLegacyPriority) {
|
|
2090
|
+
const currentState = store.getState();
|
|
2091
|
+
if (currentState.internal) {
|
|
2092
|
+
currentState.internal.priority--;
|
|
2093
|
+
let parentRoot = currentState.previousRoot;
|
|
2094
|
+
while (parentRoot) {
|
|
2095
|
+
const parentState = parentRoot.getState();
|
|
2096
|
+
if (parentState?.internal) parentState.internal.priority--;
|
|
2097
|
+
parentRoot = parentState?.previousRoot;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
1399
2102
|
} else {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
* Get ordered list of phase names
|
|
1409
|
-
*/
|
|
1410
|
-
getOrderedPhases() {
|
|
1411
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1412
|
-
return this.orderedNamesCache;
|
|
1413
|
-
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Check if a phase exists
|
|
1416
|
-
*/
|
|
1417
|
-
hasPhase(name) {
|
|
1418
|
-
return this.phaseMap.has(name);
|
|
1419
|
-
}
|
|
1420
|
-
/**
|
|
1421
|
-
* Get the index of a phase (-1 if not found)
|
|
1422
|
-
*/
|
|
1423
|
-
getPhaseIndex(name) {
|
|
1424
|
-
if (!name) return -1;
|
|
1425
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1426
|
-
}
|
|
1427
|
-
/**
|
|
1428
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1429
|
-
* Used for resolving before/after constraints.
|
|
1430
|
-
*
|
|
1431
|
-
* @param name - The phase name to ensure exists
|
|
1432
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1433
|
-
*/
|
|
1434
|
-
ensurePhase(name) {
|
|
1435
|
-
if (this.phaseMap.has(name)) return name;
|
|
1436
|
-
const node = { name, isAutoGenerated: true };
|
|
1437
|
-
this.phases.push(node);
|
|
1438
|
-
this.phaseMap.set(name, node);
|
|
1439
|
-
this.invalidateCache();
|
|
1440
|
-
return name;
|
|
1441
|
-
}
|
|
1442
|
-
/**
|
|
1443
|
-
* Resolve where a job with before/after constraints should go.
|
|
1444
|
-
* Creates auto-generated phases if needed.
|
|
1445
|
-
*
|
|
1446
|
-
* @param before - Phase(s) to run before
|
|
1447
|
-
* @param after - Phase(s) to run after
|
|
1448
|
-
* @returns The resolved phase name
|
|
1449
|
-
*/
|
|
1450
|
-
resolveConstraintPhase(before, after) {
|
|
1451
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1452
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1453
|
-
if (beforeArr.length > 0) {
|
|
1454
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1455
|
-
}
|
|
1456
|
-
if (afterArr.length > 0) {
|
|
1457
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
2103
|
+
return scheduler$1.register(
|
|
2104
|
+
(state, delta) => {
|
|
2105
|
+
const frameState = state;
|
|
2106
|
+
if (!frameState.renderer) return;
|
|
2107
|
+
callbackRef.current?.(frameState, delta);
|
|
2108
|
+
},
|
|
2109
|
+
{ id, ...options }
|
|
2110
|
+
);
|
|
1458
2111
|
}
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
/**
|
|
1462
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1463
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1464
|
-
*
|
|
1465
|
-
* @param target - The target phase name to position relative to
|
|
1466
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1467
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1468
|
-
* @returns The auto-generated phase name
|
|
1469
|
-
*/
|
|
1470
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1471
|
-
const autoName = `${prefix}:${target}`;
|
|
1472
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1473
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1474
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1475
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1476
|
-
else this.phases.push(node);
|
|
1477
|
-
this.phaseMap.set(autoName, node);
|
|
1478
|
-
this.invalidateCache();
|
|
1479
|
-
return autoName;
|
|
1480
|
-
}
|
|
1481
|
-
// Internal --------------------------------
|
|
1482
|
-
invalidateCache() {
|
|
1483
|
-
this.orderedNamesCache = null;
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1488
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1489
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1490
|
-
for (const phase of orderedPhases) {
|
|
1491
|
-
buckets.set(phase, []);
|
|
1492
|
-
}
|
|
1493
|
-
for (const job of jobs.values()) {
|
|
1494
|
-
if (!job.enabled) continue;
|
|
1495
|
-
let bucket = buckets.get(job.phase);
|
|
1496
|
-
if (!bucket) {
|
|
1497
|
-
bucket = [];
|
|
1498
|
-
buckets.set(job.phase, bucket);
|
|
1499
|
-
}
|
|
1500
|
-
bucket.push(job);
|
|
1501
|
-
}
|
|
1502
|
-
const sortedBuckets = [];
|
|
1503
|
-
for (const phase of orderedPhases) {
|
|
1504
|
-
const bucket = buckets.get(phase);
|
|
1505
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1506
|
-
bucket.sort((a, b) => {
|
|
1507
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1508
|
-
return a.index - b.index;
|
|
1509
|
-
});
|
|
1510
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1511
|
-
}
|
|
1512
|
-
for (const [phase, bucket] of buckets) {
|
|
1513
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1514
|
-
bucket.sort((a, b) => {
|
|
1515
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1516
|
-
return a.index - b.index;
|
|
1517
|
-
});
|
|
1518
|
-
sortedBuckets.push(bucket);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
return sortedBuckets.flat();
|
|
1522
|
-
}
|
|
1523
|
-
function hasCrossJobConstraints(bucket) {
|
|
1524
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1525
|
-
for (const job of bucket) {
|
|
1526
|
-
for (const ref of job.before) {
|
|
1527
|
-
if (jobIds.has(ref)) return true;
|
|
1528
|
-
}
|
|
1529
|
-
for (const ref of job.after) {
|
|
1530
|
-
if (jobIds.has(ref)) return true;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
return false;
|
|
1534
|
-
}
|
|
1535
|
-
function topologicalSort(jobs) {
|
|
1536
|
-
const n = jobs.length;
|
|
1537
|
-
if (n <= 1) return jobs;
|
|
1538
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1539
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1540
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1541
|
-
for (const job of jobs) {
|
|
1542
|
-
jobMap.set(job.id, job);
|
|
1543
|
-
inDegree.set(job.id, 0);
|
|
1544
|
-
adjacency.set(job.id, []);
|
|
1545
|
-
}
|
|
1546
|
-
for (const job of jobs) {
|
|
1547
|
-
for (const ref of job.before) {
|
|
1548
|
-
if (jobMap.has(ref)) {
|
|
1549
|
-
adjacency.get(job.id).push(ref);
|
|
1550
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
for (const ref of job.after) {
|
|
1554
|
-
if (jobMap.has(ref)) {
|
|
1555
|
-
adjacency.get(ref).push(job.id);
|
|
1556
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
const queue = [];
|
|
1561
|
-
for (const job of jobs) {
|
|
1562
|
-
if (inDegree.get(job.id) === 0) {
|
|
1563
|
-
queue.push(job);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
queue.sort((a, b) => {
|
|
1567
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1568
|
-
return a.index - b.index;
|
|
1569
|
-
});
|
|
1570
|
-
const result = [];
|
|
1571
|
-
while (queue.length > 0) {
|
|
1572
|
-
const job = queue.shift();
|
|
1573
|
-
result.push(job);
|
|
1574
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1575
|
-
for (const neighborId of neighbors) {
|
|
1576
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1577
|
-
inDegree.set(neighborId, newDegree);
|
|
1578
|
-
if (newDegree === 0) {
|
|
1579
|
-
const neighbor = jobMap.get(neighborId);
|
|
1580
|
-
insertSorted(queue, neighbor);
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
if (result.length !== n) {
|
|
1585
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1586
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1587
|
-
for (const job of jobs) {
|
|
1588
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
return result;
|
|
1592
|
-
}
|
|
1593
|
-
function insertSorted(arr, job) {
|
|
1594
|
-
let i = 0;
|
|
1595
|
-
while (i < arr.length) {
|
|
1596
|
-
const cmp = arr[i];
|
|
1597
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1598
|
-
break;
|
|
1599
|
-
}
|
|
1600
|
-
i++;
|
|
1601
|
-
}
|
|
1602
|
-
arr.splice(i, 0, job);
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
function shouldRun(job, now) {
|
|
1606
|
-
if (!job.enabled) return false;
|
|
1607
|
-
if (!job.fps) return true;
|
|
1608
|
-
const minInterval = 1e3 / job.fps;
|
|
1609
|
-
const lastRun = job.lastRun ?? 0;
|
|
1610
|
-
const elapsed = now - lastRun;
|
|
1611
|
-
if (elapsed < minInterval) return false;
|
|
1612
|
-
if (job.drop) {
|
|
1613
|
-
job.lastRun = now;
|
|
1614
|
-
} else {
|
|
1615
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1616
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1617
|
-
if (job.lastRun < now - minInterval) {
|
|
1618
|
-
job.lastRun = now;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return true;
|
|
1622
|
-
}
|
|
1623
|
-
function resetJobTiming(job) {
|
|
1624
|
-
job.lastRun = void 0;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
var __defProp$1 = Object.defineProperty;
|
|
1628
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1629
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1630
|
-
const hmrData = (() => {
|
|
1631
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1632
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1633
|
-
try {
|
|
1634
|
-
return (0, eval)("import.meta.hot");
|
|
1635
|
-
} catch {
|
|
1636
|
-
return void 0;
|
|
1637
|
-
}
|
|
1638
|
-
})();
|
|
1639
|
-
const _Scheduler = class _Scheduler {
|
|
1640
|
-
//* Constructor ================================
|
|
1641
|
-
constructor() {
|
|
1642
|
-
//* Critical State ================================
|
|
1643
|
-
__publicField$1(this, "roots", /* @__PURE__ */ new Map());
|
|
1644
|
-
__publicField$1(this, "phaseGraph");
|
|
1645
|
-
__publicField$1(this, "loopState", {
|
|
1646
|
-
running: false,
|
|
1647
|
-
rafHandle: null,
|
|
1648
|
-
lastTime: null,
|
|
1649
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1650
|
-
frameCount: 0,
|
|
1651
|
-
elapsedTime: 0,
|
|
1652
|
-
createdAt: performance.now()
|
|
1653
|
-
});
|
|
1654
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1655
|
-
//* Private State ================================
|
|
1656
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1657
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1658
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1659
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1660
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1661
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1662
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1663
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1664
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1665
|
-
//* Independent Mode & Error Handling State ================================
|
|
1666
|
-
__publicField$1(this, "_independent", false);
|
|
1667
|
-
__publicField$1(this, "errorHandler", null);
|
|
1668
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1669
|
-
//* Core Loop Execution Methods ================================
|
|
1670
|
-
/**
|
|
1671
|
-
* Main RAF loop callback.
|
|
1672
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1673
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1674
|
-
* @returns {void}
|
|
1675
|
-
* @private
|
|
1676
|
-
*/
|
|
1677
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1678
|
-
if (!this.loopState.running) return;
|
|
1679
|
-
this.executeFrame(timestamp);
|
|
1680
|
-
if (this._frameloop === "demand") {
|
|
1681
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1682
|
-
if (this.pendingFrames === 0) {
|
|
1683
|
-
this.notifyIdle(timestamp);
|
|
1684
|
-
return this.stop();
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1688
|
-
});
|
|
1689
|
-
this.phaseGraph = new PhaseGraph();
|
|
1690
|
-
}
|
|
1691
|
-
static get instance() {
|
|
1692
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1693
|
-
}
|
|
1694
|
-
static set instance(value) {
|
|
1695
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1696
|
-
}
|
|
1697
|
-
/**
|
|
1698
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1699
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1700
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1701
|
-
*/
|
|
1702
|
-
static get() {
|
|
1703
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1704
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1705
|
-
}
|
|
1706
|
-
if (!_Scheduler.instance) {
|
|
1707
|
-
_Scheduler.instance = new _Scheduler();
|
|
1708
|
-
if (hmrData?.data) {
|
|
1709
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
return _Scheduler.instance;
|
|
1713
|
-
}
|
|
1714
|
-
/**
|
|
1715
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1716
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1717
|
-
* @returns {void}
|
|
1718
|
-
*/
|
|
1719
|
-
static reset() {
|
|
1720
|
-
if (_Scheduler.instance) {
|
|
1721
|
-
_Scheduler.instance.stop();
|
|
1722
|
-
_Scheduler.instance = null;
|
|
1723
|
-
}
|
|
1724
|
-
if (hmrData?.data) {
|
|
1725
|
-
hmrData.data.scheduler = null;
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
//* Getters & Setters ================================
|
|
1729
|
-
get phases() {
|
|
1730
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1731
|
-
}
|
|
1732
|
-
get frameloop() {
|
|
1733
|
-
return this._frameloop;
|
|
1734
|
-
}
|
|
1735
|
-
set frameloop(mode) {
|
|
1736
|
-
if (this._frameloop === mode) return;
|
|
1737
|
-
const wasAlways = this._frameloop === "always";
|
|
1738
|
-
this._frameloop = mode;
|
|
1739
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1740
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1741
|
-
}
|
|
1742
|
-
get isRunning() {
|
|
1743
|
-
return this.loopState.running;
|
|
1744
|
-
}
|
|
1745
|
-
get isReady() {
|
|
1746
|
-
return this.roots.size > 0;
|
|
1747
|
-
}
|
|
1748
|
-
get independent() {
|
|
1749
|
-
return this._independent;
|
|
1750
|
-
}
|
|
1751
|
-
set independent(value) {
|
|
1752
|
-
this._independent = value;
|
|
1753
|
-
if (value) this.ensureDefaultRoot();
|
|
1754
|
-
}
|
|
1755
|
-
//* Root Management Methods ================================
|
|
1756
|
-
/**
|
|
1757
|
-
* Register a root (Canvas) with the scheduler.
|
|
1758
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1759
|
-
* @param {string} id - Unique identifier for this root
|
|
1760
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1761
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1762
|
-
*/
|
|
1763
|
-
registerRoot(id, options = {}) {
|
|
1764
|
-
if (this.roots.has(id)) {
|
|
1765
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1766
|
-
return () => this.unregisterRoot(id);
|
|
1767
|
-
}
|
|
1768
|
-
const entry = {
|
|
1769
|
-
id,
|
|
1770
|
-
getState: options.getState ?? (() => ({})),
|
|
1771
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1772
|
-
sortedJobs: [],
|
|
1773
|
-
needsRebuild: false
|
|
1774
|
-
};
|
|
1775
|
-
if (options.onError) {
|
|
1776
|
-
this.errorHandler = options.onError;
|
|
1777
|
-
}
|
|
1778
|
-
this.roots.set(id, entry);
|
|
1779
|
-
if (this.roots.size === 1) {
|
|
1780
|
-
this.notifyRootReady();
|
|
1781
|
-
if (this._frameloop === "always") this.start();
|
|
1782
|
-
}
|
|
1783
|
-
return () => this.unregisterRoot(id);
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Unregister a root from the scheduler.
|
|
1787
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1788
|
-
* The last root to unregister stops the RAF loop.
|
|
1789
|
-
* @param {string} id - The root ID to unregister
|
|
1790
|
-
* @returns {void}
|
|
1791
|
-
*/
|
|
1792
|
-
unregisterRoot(id) {
|
|
1793
|
-
const root = this.roots.get(id);
|
|
1794
|
-
if (!root) return;
|
|
1795
|
-
for (const jobId of root.jobs.keys()) {
|
|
1796
|
-
this.jobStateListeners.delete(jobId);
|
|
1797
|
-
}
|
|
1798
|
-
this.roots.delete(id);
|
|
1799
|
-
if (this.roots.size === 0) {
|
|
1800
|
-
this.stop();
|
|
1801
|
-
this.errorHandler = null;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
/**
|
|
1805
|
-
* Subscribe to be notified when a root becomes available.
|
|
1806
|
-
* Fires immediately if a root already exists.
|
|
1807
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1808
|
-
* @returns {() => void} Unsubscribe function
|
|
1809
|
-
*/
|
|
1810
|
-
onRootReady(callback) {
|
|
1811
|
-
if (this.roots.size > 0) {
|
|
1812
|
-
callback();
|
|
1813
|
-
return () => {
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
this.rootReadyCallbacks.add(callback);
|
|
1817
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1818
|
-
}
|
|
1819
|
-
/**
|
|
1820
|
-
* Notify all registered root-ready callbacks.
|
|
1821
|
-
* Called when the first root registers.
|
|
1822
|
-
* @returns {void}
|
|
1823
|
-
* @private
|
|
1824
|
-
*/
|
|
1825
|
-
notifyRootReady() {
|
|
1826
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1827
|
-
try {
|
|
1828
|
-
cb();
|
|
1829
|
-
} catch (error) {
|
|
1830
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
this.rootReadyCallbacks.clear();
|
|
1834
|
-
}
|
|
1835
|
-
/**
|
|
1836
|
-
* Ensure a default root exists for independent mode.
|
|
1837
|
-
* Creates a minimal root with no state provider.
|
|
1838
|
-
* @returns {void}
|
|
1839
|
-
* @private
|
|
1840
|
-
*/
|
|
1841
|
-
ensureDefaultRoot() {
|
|
1842
|
-
if (!this.roots.has("__default__")) {
|
|
1843
|
-
this.registerRoot("__default__");
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
/**
|
|
1847
|
-
* Trigger error handling for job errors.
|
|
1848
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1849
|
-
* @param {Error} error - The error to handle
|
|
1850
|
-
* @returns {void}
|
|
1851
|
-
*/
|
|
1852
|
-
triggerError(error) {
|
|
1853
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1854
|
-
else console.error("[Scheduler]", error);
|
|
1855
|
-
}
|
|
1856
|
-
//* Phase Management Methods ================================
|
|
1857
|
-
/**
|
|
1858
|
-
* Add a named phase to the scheduler's execution order.
|
|
1859
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1860
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1861
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1862
|
-
* @returns {void}
|
|
1863
|
-
* @example
|
|
1864
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1865
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1866
|
-
*/
|
|
1867
|
-
addPhase(name, options) {
|
|
1868
|
-
this.phaseGraph.addPhase(name, options);
|
|
1869
|
-
for (const root of this.roots.values()) {
|
|
1870
|
-
root.needsRebuild = true;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
/**
|
|
1874
|
-
* Check if a phase exists in the scheduler.
|
|
1875
|
-
* @param {string} name - The phase name to check
|
|
1876
|
-
* @returns {boolean} True if the phase exists
|
|
1877
|
-
*/
|
|
1878
|
-
hasPhase(name) {
|
|
1879
|
-
return this.phaseGraph.hasPhase(name);
|
|
1880
|
-
}
|
|
1881
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1882
|
-
/**
|
|
1883
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1884
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1885
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1886
|
-
* @param {string} id - Unique identifier for this global job
|
|
1887
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1888
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1889
|
-
* @deprecated Use useFrame with phases instead
|
|
1890
|
-
*/
|
|
1891
|
-
registerGlobal(phase, id, callback) {
|
|
1892
|
-
const job = { id, callback };
|
|
1893
|
-
if (phase === "before") {
|
|
1894
|
-
this.globalBeforeJobs.set(id, job);
|
|
1895
|
-
} else {
|
|
1896
|
-
this.globalAfterJobs.set(id, job);
|
|
1897
|
-
}
|
|
1898
|
-
return () => {
|
|
1899
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1900
|
-
else this.globalAfterJobs.delete(id);
|
|
1901
|
-
};
|
|
1902
|
-
}
|
|
1903
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1904
|
-
/**
|
|
1905
|
-
* Register an idle callback that fires when the loop stops.
|
|
1906
|
-
* Used internally by deprecated addTail API.
|
|
1907
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1908
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1909
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1910
|
-
*/
|
|
1911
|
-
onIdle(callback) {
|
|
1912
|
-
this.idleCallbacks.add(callback);
|
|
1913
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1914
|
-
}
|
|
1915
|
-
/**
|
|
1916
|
-
* Notify all registered idle callbacks.
|
|
1917
|
-
* Called when the loop stops in demand mode.
|
|
1918
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1919
|
-
* @returns {void}
|
|
1920
|
-
* @private
|
|
1921
|
-
*/
|
|
1922
|
-
notifyIdle(timestamp) {
|
|
1923
|
-
for (const cb of this.idleCallbacks) {
|
|
1924
|
-
try {
|
|
1925
|
-
cb(timestamp);
|
|
1926
|
-
} catch (error) {
|
|
1927
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
//* Job Registration & Management Methods ================================
|
|
1932
|
-
/**
|
|
1933
|
-
* Register a job (frame callback) with a specific root.
|
|
1934
|
-
* This is the core registration method used by useFrame internally.
|
|
1935
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1936
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1937
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1938
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1939
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1940
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1941
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1942
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1943
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1944
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1945
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1946
|
-
*/
|
|
1947
|
-
register(callback, options = {}) {
|
|
1948
|
-
const rootId = options.rootId;
|
|
1949
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1950
|
-
if (!root) {
|
|
1951
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1952
|
-
return () => {
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
const id = options.id ?? this.generateJobId();
|
|
1956
|
-
let phase = options.phase ?? "update";
|
|
1957
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1958
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1959
|
-
}
|
|
1960
|
-
const before = this.normalizeConstraints(options.before);
|
|
1961
|
-
const after = this.normalizeConstraints(options.after);
|
|
1962
|
-
const job = {
|
|
1963
|
-
id,
|
|
1964
|
-
callback,
|
|
1965
|
-
phase,
|
|
1966
|
-
before,
|
|
1967
|
-
after,
|
|
1968
|
-
priority: options.priority ?? 0,
|
|
1969
|
-
index: this.nextJobIndex++,
|
|
1970
|
-
fps: options.fps,
|
|
1971
|
-
drop: options.drop ?? true,
|
|
1972
|
-
enabled: options.enabled ?? true,
|
|
1973
|
-
system: options.system ?? false
|
|
1974
|
-
};
|
|
1975
|
-
if (root.jobs.has(id)) {
|
|
1976
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1977
|
-
}
|
|
1978
|
-
root.jobs.set(id, job);
|
|
1979
|
-
root.needsRebuild = true;
|
|
1980
|
-
return () => this.unregister(id, root.id);
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Unregister a job by its ID.
|
|
1984
|
-
* Searches all roots if rootId is not provided.
|
|
1985
|
-
* @param {string} id - The job ID to unregister
|
|
1986
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1987
|
-
* @returns {void}
|
|
1988
|
-
*/
|
|
1989
|
-
unregister(id, rootId) {
|
|
1990
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1991
|
-
if (root?.jobs.delete(id)) {
|
|
1992
|
-
root.needsRebuild = true;
|
|
1993
|
-
this.jobStateListeners.delete(id);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Update a job's options dynamically.
|
|
1998
|
-
* Searches all roots to find the job by ID.
|
|
1999
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2000
|
-
* @param {string} id - The job ID to update
|
|
2001
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2002
|
-
* @returns {void}
|
|
2003
|
-
*/
|
|
2004
|
-
updateJob(id, options) {
|
|
2005
|
-
let job;
|
|
2006
|
-
let root;
|
|
2007
|
-
for (const r of this.roots.values()) {
|
|
2008
|
-
job = r.jobs.get(id);
|
|
2009
|
-
if (job) {
|
|
2010
|
-
root = r;
|
|
2011
|
-
break;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
if (!job || !root) return;
|
|
2015
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2016
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2017
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2018
|
-
if (options.enabled !== void 0) {
|
|
2019
|
-
const wasEnabled = job.enabled;
|
|
2020
|
-
job.enabled = options.enabled;
|
|
2021
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2022
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2023
|
-
}
|
|
2024
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2025
|
-
if (options.phase) job.phase = options.phase;
|
|
2026
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2027
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2028
|
-
root.needsRebuild = true;
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
//* Job State Management Methods ================================
|
|
2032
|
-
/**
|
|
2033
|
-
* Check if a job is currently paused (disabled).
|
|
2034
|
-
* @param {string} id - The job ID to check
|
|
2035
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2036
|
-
*/
|
|
2037
|
-
isJobPaused(id) {
|
|
2038
|
-
for (const root of this.roots.values()) {
|
|
2039
|
-
const job = root.jobs.get(id);
|
|
2040
|
-
if (job) return !job.enabled;
|
|
2041
|
-
}
|
|
2042
|
-
return false;
|
|
2043
|
-
}
|
|
2044
|
-
/**
|
|
2045
|
-
* Subscribe to state changes for a specific job.
|
|
2046
|
-
* Listener is called when job is paused or resumed.
|
|
2047
|
-
* @param {string} id - The job ID to subscribe to
|
|
2048
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2049
|
-
* @returns {() => void} Unsubscribe function
|
|
2050
|
-
*/
|
|
2051
|
-
subscribeJobState(id, listener) {
|
|
2052
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2053
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2054
|
-
}
|
|
2055
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2056
|
-
return () => {
|
|
2057
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2058
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2059
|
-
this.jobStateListeners.delete(id);
|
|
2060
|
-
}
|
|
2061
|
-
};
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Notify all listeners that a job's state has changed.
|
|
2065
|
-
* @param {string} id - The job ID that changed
|
|
2066
|
-
* @returns {void}
|
|
2067
|
-
* @private
|
|
2068
|
-
*/
|
|
2069
|
-
notifyJobStateChange(id) {
|
|
2070
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Pause a job by ID (sets enabled=false).
|
|
2074
|
-
* Notifies any subscribed state listeners.
|
|
2075
|
-
* @param {string} id - The job ID to pause
|
|
2076
|
-
* @returns {void}
|
|
2077
|
-
*/
|
|
2078
|
-
pauseJob(id) {
|
|
2079
|
-
this.updateJob(id, { enabled: false });
|
|
2080
|
-
this.notifyJobStateChange(id);
|
|
2081
|
-
}
|
|
2082
|
-
/**
|
|
2083
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2084
|
-
* Resets job timing to prevent frame accumulation.
|
|
2085
|
-
* Notifies any subscribed state listeners.
|
|
2086
|
-
* @param {string} id - The job ID to resume
|
|
2087
|
-
* @returns {void}
|
|
2088
|
-
*/
|
|
2089
|
-
resumeJob(id) {
|
|
2090
|
-
this.updateJob(id, { enabled: true });
|
|
2091
|
-
this.notifyJobStateChange(id);
|
|
2092
|
-
}
|
|
2093
|
-
//* Frame Loop Control Methods ================================
|
|
2094
|
-
/**
|
|
2095
|
-
* Start the requestAnimationFrame loop.
|
|
2096
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2097
|
-
* No-op if already running.
|
|
2098
|
-
* @returns {void}
|
|
2099
|
-
*/
|
|
2100
|
-
start() {
|
|
2101
|
-
if (this.loopState.running) return;
|
|
2102
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2103
|
-
let adjustedCreated = 0;
|
|
2104
|
-
if (this.stoppedTime > 0) {
|
|
2105
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2106
|
-
this.stoppedTime = 0;
|
|
2107
|
-
}
|
|
2108
|
-
Object.assign(this.loopState, {
|
|
2109
|
-
running: true,
|
|
2110
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2111
|
-
lastTime: performance.now(),
|
|
2112
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2113
|
-
frameCount: 0,
|
|
2114
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2115
|
-
});
|
|
2116
|
-
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Stop the requestAnimationFrame loop.
|
|
2119
|
-
* Cancels any pending RAF callback.
|
|
2120
|
-
* No-op if not running.
|
|
2121
|
-
* @returns {void}
|
|
2122
|
-
*/
|
|
2123
|
-
stop() {
|
|
2124
|
-
if (!this.loopState.running) return;
|
|
2125
|
-
this.loopState.running = false;
|
|
2126
|
-
if (this.loopState.rafHandle !== null) {
|
|
2127
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2128
|
-
this.loopState.rafHandle = null;
|
|
2129
|
-
}
|
|
2130
|
-
this.stoppedTime = performance.now();
|
|
2131
|
-
}
|
|
2132
|
-
/**
|
|
2133
|
-
* Request frames to be rendered in demand mode.
|
|
2134
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2135
|
-
* No-op if frameloop is not 'demand'.
|
|
2136
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2137
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2138
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2139
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2140
|
-
* @returns {void}
|
|
2141
|
-
* @example
|
|
2142
|
-
* // Request a single frame render
|
|
2143
|
-
* scheduler.invalidate();
|
|
2144
|
-
*
|
|
2145
|
-
* @example
|
|
2146
|
-
* // Request 5 frames (e.g., for animations)
|
|
2147
|
-
* scheduler.invalidate(5);
|
|
2148
|
-
*
|
|
2149
|
-
* @example
|
|
2150
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2151
|
-
* scheduler.invalidate(3, false);
|
|
2152
|
-
*
|
|
2153
|
-
* @example
|
|
2154
|
-
* // Add 2 more frames to existing pending count
|
|
2155
|
-
* scheduler.invalidate(2, true);
|
|
2156
|
-
*/
|
|
2157
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2158
|
-
if (this._frameloop !== "demand") return;
|
|
2159
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2160
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2161
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2162
|
-
}
|
|
2163
|
-
/**
|
|
2164
|
-
* Reset timing state for deterministic testing.
|
|
2165
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2166
|
-
* @returns {void}
|
|
2167
|
-
*/
|
|
2168
|
-
resetTiming() {
|
|
2169
|
-
this.loopState.lastTime = null;
|
|
2170
|
-
this.loopState.frameCount = 0;
|
|
2171
|
-
this.loopState.elapsedTime = 0;
|
|
2172
|
-
this.loopState.createdAt = performance.now();
|
|
2173
|
-
}
|
|
2174
|
-
//* Manual Stepping Methods ================================
|
|
2175
|
-
/**
|
|
2176
|
-
* Manually execute a single frame for all roots.
|
|
2177
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2178
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2179
|
-
* @returns {void}
|
|
2180
|
-
* @example
|
|
2181
|
-
* // Manual control mode
|
|
2182
|
-
* scheduler.frameloop = 'never';
|
|
2183
|
-
* scheduler.step(); // Execute one frame
|
|
2184
|
-
*/
|
|
2185
|
-
step(timestamp) {
|
|
2186
|
-
const now = timestamp ?? performance.now();
|
|
2187
|
-
this.executeFrame(now);
|
|
2188
|
-
}
|
|
2189
|
-
/**
|
|
2190
|
-
* Manually execute a single job by its ID.
|
|
2191
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2192
|
-
* @param {string} id - The job ID to step
|
|
2193
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2194
|
-
* @returns {void}
|
|
2195
|
-
*/
|
|
2196
|
-
stepJob(id, timestamp) {
|
|
2197
|
-
let job;
|
|
2198
|
-
let root;
|
|
2199
|
-
for (const r of this.roots.values()) {
|
|
2200
|
-
job = r.jobs.get(id);
|
|
2201
|
-
if (job) {
|
|
2202
|
-
root = r;
|
|
2203
|
-
break;
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
if (!job || !root) {
|
|
2207
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
const now = timestamp ?? performance.now();
|
|
2211
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2212
|
-
const delta = deltaMs / 1e3;
|
|
2213
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2214
|
-
const providedState = root.getState?.() ?? {};
|
|
2215
|
-
const frameState = {
|
|
2216
|
-
...providedState,
|
|
2217
|
-
time: now,
|
|
2218
|
-
delta,
|
|
2219
|
-
elapsed,
|
|
2220
|
-
frame: this.loopState.frameCount
|
|
2221
|
-
};
|
|
2222
|
-
try {
|
|
2223
|
-
job.callback(frameState, delta);
|
|
2224
|
-
} catch (error) {
|
|
2225
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2226
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
/**
|
|
2230
|
-
* Execute a single frame across all roots.
|
|
2231
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2232
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2233
|
-
* @returns {void}
|
|
2234
|
-
* @private
|
|
2235
|
-
*/
|
|
2236
|
-
executeFrame(timestamp) {
|
|
2237
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2238
|
-
const delta = deltaMs / 1e3;
|
|
2239
|
-
this.loopState.lastTime = timestamp;
|
|
2240
|
-
this.loopState.frameCount++;
|
|
2241
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2242
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2243
|
-
for (const root of this.roots.values()) {
|
|
2244
|
-
this.tickRoot(root, timestamp, delta);
|
|
2245
|
-
}
|
|
2246
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2247
|
-
}
|
|
2248
|
-
/**
|
|
2249
|
-
* Run all global jobs from a job map.
|
|
2250
|
-
* Catches and logs errors without stopping execution.
|
|
2251
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2252
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2253
|
-
* @returns {void}
|
|
2254
|
-
* @private
|
|
2255
|
-
*/
|
|
2256
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2257
|
-
for (const job of jobs.values()) {
|
|
2258
|
-
try {
|
|
2259
|
-
job.callback(timestamp);
|
|
2260
|
-
} catch (error) {
|
|
2261
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
/**
|
|
2266
|
-
* Execute all jobs for a single root in sorted order.
|
|
2267
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2268
|
-
* Errors are caught and propagated via triggerError.
|
|
2269
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2270
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2271
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2272
|
-
* @returns {void}
|
|
2273
|
-
* @private
|
|
2274
|
-
*/
|
|
2275
|
-
tickRoot(root, timestamp, delta) {
|
|
2276
|
-
if (root.needsRebuild) {
|
|
2277
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2278
|
-
root.needsRebuild = false;
|
|
2279
|
-
}
|
|
2280
|
-
const providedState = root.getState?.() ?? {};
|
|
2281
|
-
const frameState = {
|
|
2282
|
-
...providedState,
|
|
2283
|
-
time: timestamp,
|
|
2284
|
-
delta,
|
|
2285
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2286
|
-
// Convert ms to seconds
|
|
2287
|
-
frame: this.loopState.frameCount
|
|
2288
|
-
};
|
|
2289
|
-
for (const job of root.sortedJobs) {
|
|
2290
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2291
|
-
try {
|
|
2292
|
-
job.callback(frameState, delta);
|
|
2293
|
-
} catch (error) {
|
|
2294
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2295
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
//* Debug & Inspection Methods ================================
|
|
2300
|
-
/**
|
|
2301
|
-
* Get the total number of registered jobs across all roots.
|
|
2302
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2303
|
-
* @returns {number} Total job count
|
|
2304
|
-
*/
|
|
2305
|
-
getJobCount() {
|
|
2306
|
-
let count = 0;
|
|
2307
|
-
for (const root of this.roots.values()) {
|
|
2308
|
-
count += root.jobs.size;
|
|
2309
|
-
}
|
|
2310
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2311
|
-
}
|
|
2312
|
-
/**
|
|
2313
|
-
* Get all registered job IDs across all roots.
|
|
2314
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2315
|
-
* @returns {string[]} Array of all job IDs
|
|
2316
|
-
*/
|
|
2317
|
-
getJobIds() {
|
|
2318
|
-
const ids = [];
|
|
2319
|
-
for (const root of this.roots.values()) {
|
|
2320
|
-
ids.push(...root.jobs.keys());
|
|
2321
|
-
}
|
|
2322
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2323
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2324
|
-
return ids;
|
|
2325
|
-
}
|
|
2326
|
-
/**
|
|
2327
|
-
* Get the number of registered roots (Canvas instances).
|
|
2328
|
-
* @returns {number} Number of registered roots
|
|
2329
|
-
*/
|
|
2330
|
-
getRootCount() {
|
|
2331
|
-
return this.roots.size;
|
|
2332
|
-
}
|
|
2333
|
-
/**
|
|
2334
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2335
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2336
|
-
*
|
|
2337
|
-
* @param phase The phase to check
|
|
2338
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2339
|
-
* @returns true if any user jobs exist in the phase
|
|
2340
|
-
*/
|
|
2341
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2342
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2343
|
-
return rootsToCheck.some((root) => {
|
|
2344
|
-
if (!root) return false;
|
|
2345
|
-
for (const job of root.jobs.values()) {
|
|
2346
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2347
|
-
}
|
|
2348
|
-
return false;
|
|
2349
|
-
});
|
|
2350
|
-
}
|
|
2351
|
-
//* Utility Methods ================================
|
|
2352
|
-
/**
|
|
2353
|
-
* Generate a unique root ID for automatic root registration.
|
|
2354
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2355
|
-
*/
|
|
2356
|
-
generateRootId() {
|
|
2357
|
-
return `root_${this.nextRootIndex++}`;
|
|
2358
|
-
}
|
|
2359
|
-
/**
|
|
2360
|
-
* Generate a unique job ID.
|
|
2361
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2362
|
-
* @private
|
|
2363
|
-
*/
|
|
2364
|
-
generateJobId() {
|
|
2365
|
-
return `job_${this.nextJobIndex}`;
|
|
2366
|
-
}
|
|
2367
|
-
/**
|
|
2368
|
-
* Normalize before/after constraints to a Set.
|
|
2369
|
-
* Handles undefined, single string, or array inputs.
|
|
2370
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2371
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2372
|
-
* @private
|
|
2373
|
-
*/
|
|
2374
|
-
normalizeConstraints(value) {
|
|
2375
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2376
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2377
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2378
|
-
}
|
|
2379
|
-
};
|
|
2380
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2381
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2382
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2383
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2384
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2385
|
-
let Scheduler = _Scheduler;
|
|
2386
|
-
const getScheduler = () => Scheduler.get();
|
|
2387
|
-
if (hmrData) {
|
|
2388
|
-
hmrData.accept?.();
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
function useFrame(callback, priorityOrOptions) {
|
|
2392
|
-
const store = React__namespace.useContext(context);
|
|
2393
|
-
const isInsideCanvas = store !== null;
|
|
2394
|
-
const scheduler = getScheduler();
|
|
2395
|
-
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2396
|
-
id: priorityOrOptions.id,
|
|
2397
|
-
phase: priorityOrOptions.phase,
|
|
2398
|
-
priority: priorityOrOptions.priority,
|
|
2399
|
-
fps: priorityOrOptions.fps,
|
|
2400
|
-
drop: priorityOrOptions.drop,
|
|
2401
|
-
enabled: priorityOrOptions.enabled,
|
|
2402
|
-
before: priorityOrOptions.before,
|
|
2403
|
-
after: priorityOrOptions.after
|
|
2404
|
-
}) : "";
|
|
2405
|
-
const options = React__namespace.useMemo(() => {
|
|
2406
|
-
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2407
|
-
}, [optionsKey]);
|
|
2408
|
-
const reactId = React__namespace.useId();
|
|
2409
|
-
const id = options.id ?? reactId;
|
|
2410
|
-
const callbackRef = useMutableCallback(callback);
|
|
2411
|
-
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2412
|
-
useIsomorphicLayoutEffect(() => {
|
|
2413
|
-
if (!callback) return;
|
|
2414
|
-
if (isInsideCanvas) {
|
|
2415
|
-
const state = store.getState();
|
|
2416
|
-
const rootId = state.internal.rootId;
|
|
2417
|
-
if (isLegacyPriority) {
|
|
2418
|
-
state.internal.priority++;
|
|
2419
|
-
let parentRoot = state.previousRoot;
|
|
2420
|
-
while (parentRoot) {
|
|
2421
|
-
const parentState = parentRoot.getState();
|
|
2422
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2423
|
-
parentRoot = parentState?.previousRoot;
|
|
2424
|
-
}
|
|
2425
|
-
notifyDepreciated({
|
|
2426
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2427
|
-
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2428
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2429
|
-
});
|
|
2430
|
-
}
|
|
2431
|
-
const wrappedCallback = (frameState, delta) => {
|
|
2432
|
-
const localState = store.getState();
|
|
2433
|
-
const mergedState = {
|
|
2434
|
-
...localState,
|
|
2435
|
-
time: frameState.time,
|
|
2436
|
-
delta: frameState.delta,
|
|
2437
|
-
elapsed: frameState.elapsed,
|
|
2438
|
-
frame: frameState.frame
|
|
2439
|
-
};
|
|
2440
|
-
callbackRef.current?.(mergedState, delta);
|
|
2441
|
-
};
|
|
2442
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2443
|
-
id,
|
|
2444
|
-
rootId,
|
|
2445
|
-
...options
|
|
2446
|
-
});
|
|
2447
|
-
return () => {
|
|
2448
|
-
unregister();
|
|
2449
|
-
if (isLegacyPriority) {
|
|
2450
|
-
const currentState = store.getState();
|
|
2451
|
-
if (currentState.internal) {
|
|
2452
|
-
currentState.internal.priority--;
|
|
2453
|
-
let parentRoot = currentState.previousRoot;
|
|
2454
|
-
while (parentRoot) {
|
|
2455
|
-
const parentState = parentRoot.getState();
|
|
2456
|
-
if (parentState?.internal) parentState.internal.priority--;
|
|
2457
|
-
parentRoot = parentState?.previousRoot;
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
};
|
|
2462
|
-
} else {
|
|
2463
|
-
const registerOutside = () => {
|
|
2464
|
-
return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
|
|
2465
|
-
};
|
|
2466
|
-
if (scheduler.independent || scheduler.isReady) {
|
|
2467
|
-
return registerOutside();
|
|
2468
|
-
}
|
|
2469
|
-
let unregisterJob = null;
|
|
2470
|
-
const unsubReady = scheduler.onRootReady(() => {
|
|
2471
|
-
unregisterJob = registerOutside();
|
|
2472
|
-
});
|
|
2473
|
-
return () => {
|
|
2474
|
-
unsubReady();
|
|
2475
|
-
unregisterJob?.();
|
|
2476
|
-
};
|
|
2477
|
-
}
|
|
2478
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2112
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2479
2113
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2480
2114
|
// Subscribe function
|
|
2481
2115
|
React__namespace.useCallback(
|
|
2482
2116
|
(onStoreChange) => {
|
|
2483
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2117
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2484
2118
|
},
|
|
2485
2119
|
[id]
|
|
2486
2120
|
),
|
|
2487
2121
|
// getSnapshot function
|
|
2488
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2122
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2489
2123
|
// getServerSnapshot function (SSR)
|
|
2490
2124
|
React__namespace.useCallback(() => false, [])
|
|
2491
2125
|
);
|
|
2492
2126
|
const controls = React__namespace.useMemo(() => {
|
|
2493
|
-
const scheduler2 = getScheduler();
|
|
2127
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2494
2128
|
return {
|
|
2495
2129
|
/** The job's unique ID */
|
|
2496
2130
|
id,
|
|
@@ -2505,7 +2139,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2505
2139
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2506
2140
|
*/
|
|
2507
2141
|
step: (timestamp) => {
|
|
2508
|
-
getScheduler().stepJob(id, timestamp);
|
|
2142
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2509
2143
|
},
|
|
2510
2144
|
/**
|
|
2511
2145
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2513,20 +2147,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2513
2147
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2514
2148
|
*/
|
|
2515
2149
|
stepAll: (timestamp) => {
|
|
2516
|
-
getScheduler().step(timestamp);
|
|
2150
|
+
scheduler.getScheduler().step(timestamp);
|
|
2517
2151
|
},
|
|
2518
2152
|
/**
|
|
2519
2153
|
* Pause this job (set enabled=false).
|
|
2520
2154
|
* Job remains registered but won't run.
|
|
2521
2155
|
*/
|
|
2522
2156
|
pause: () => {
|
|
2523
|
-
getScheduler().pauseJob(id);
|
|
2157
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2524
2158
|
},
|
|
2525
2159
|
/**
|
|
2526
2160
|
* Resume this job (set enabled=true).
|
|
2527
2161
|
*/
|
|
2528
2162
|
resume: () => {
|
|
2529
|
-
getScheduler().resumeJob(id);
|
|
2163
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2530
2164
|
},
|
|
2531
2165
|
/**
|
|
2532
2166
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2564,22 +2198,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2564
2198
|
function useTexture(input, optionsOrOnLoad) {
|
|
2565
2199
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2566
2200
|
const store = useStore();
|
|
2567
|
-
const textureCache = useThree((state) => state.textures);
|
|
2568
2201
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2569
|
-
const { onLoad, cache =
|
|
2202
|
+
const { onLoad, cache = true } = options;
|
|
2203
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2204
|
+
onLoadRef.current = onLoad;
|
|
2205
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2570
2206
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2571
2207
|
const cachedResult = React.useMemo(() => {
|
|
2572
2208
|
if (!cache) return null;
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2209
|
+
const textures = store.getState().textures;
|
|
2210
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2211
|
+
return buildFromCache(input, textures);
|
|
2212
|
+
}, [cache, urls, input, store]);
|
|
2576
2213
|
const loadedTextures = useLoader(
|
|
2577
2214
|
webgpu.TextureLoader,
|
|
2578
2215
|
IsObject(input) ? Object.values(input) : input
|
|
2579
2216
|
);
|
|
2217
|
+
const inputKey = urls.join("\0");
|
|
2580
2218
|
React.useLayoutEffect(() => {
|
|
2581
|
-
if (
|
|
2582
|
-
|
|
2219
|
+
if (cachedResult) return;
|
|
2220
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2221
|
+
onLoadCalledForRef.current = inputKey;
|
|
2222
|
+
onLoadRef.current?.(loadedTextures);
|
|
2223
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2583
2224
|
React.useEffect(() => {
|
|
2584
2225
|
if (cachedResult) return;
|
|
2585
2226
|
if ("initTexture" in renderer) {
|
|
@@ -2612,8 +2253,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2612
2253
|
}, [input, loadedTextures, cachedResult]);
|
|
2613
2254
|
React.useEffect(() => {
|
|
2614
2255
|
if (!cache) return;
|
|
2615
|
-
if (cachedResult) return;
|
|
2616
|
-
const set = store.setState;
|
|
2617
2256
|
const urlTextureMap = [];
|
|
2618
2257
|
if (typeof input === "string") {
|
|
2619
2258
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2627,18 +2266,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2627
2266
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2628
2267
|
}
|
|
2629
2268
|
}
|
|
2630
|
-
|
|
2631
|
-
const
|
|
2632
|
-
let
|
|
2269
|
+
store.setState((state) => {
|
|
2270
|
+
const refs = new Map(state._textureRefs);
|
|
2271
|
+
let textures = state.textures;
|
|
2272
|
+
let added = false;
|
|
2633
2273
|
for (const [url, texture] of urlTextureMap) {
|
|
2634
|
-
if (!
|
|
2635
|
-
|
|
2636
|
-
|
|
2274
|
+
if (!textures.has(url)) {
|
|
2275
|
+
if (!added) {
|
|
2276
|
+
textures = new Map(textures);
|
|
2277
|
+
added = true;
|
|
2278
|
+
}
|
|
2279
|
+
textures.set(url, texture);
|
|
2637
2280
|
}
|
|
2281
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2638
2282
|
}
|
|
2639
|
-
return
|
|
2283
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2640
2284
|
});
|
|
2641
|
-
|
|
2285
|
+
return () => store.setState((state) => {
|
|
2286
|
+
const refs = new Map(state._textureRefs);
|
|
2287
|
+
for (const [url] of urlTextureMap) {
|
|
2288
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2289
|
+
if (next <= 0) refs.delete(url);
|
|
2290
|
+
else refs.set(url, next);
|
|
2291
|
+
}
|
|
2292
|
+
return { _textureRefs: refs };
|
|
2293
|
+
});
|
|
2294
|
+
}, [cache, input, mappedTextures, store]);
|
|
2642
2295
|
return mappedTextures;
|
|
2643
2296
|
}
|
|
2644
2297
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2654,106 +2307,90 @@ const Texture = ({
|
|
|
2654
2307
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2655
2308
|
};
|
|
2656
2309
|
|
|
2657
|
-
function
|
|
2658
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
2659
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
2660
|
-
return entry.value;
|
|
2661
|
-
}
|
|
2662
|
-
return null;
|
|
2663
|
-
}
|
|
2664
|
-
function useTextures() {
|
|
2310
|
+
function useTextures(selector) {
|
|
2665
2311
|
const store = useStore();
|
|
2666
|
-
|
|
2667
|
-
const set = store.setState;
|
|
2312
|
+
const registry = React.useMemo(() => {
|
|
2668
2313
|
const getState = store.getState;
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
const newMap = new Map(state.textures);
|
|
2672
|
-
newMap.set(key, value);
|
|
2673
|
-
return { textures: newMap };
|
|
2674
|
-
});
|
|
2675
|
-
};
|
|
2676
|
-
const addMultiple = (items) => {
|
|
2677
|
-
set((state) => {
|
|
2678
|
-
const newMap = new Map(state.textures);
|
|
2679
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2680
|
-
for (const [key, value] of entries) {
|
|
2681
|
-
newMap.set(key, value);
|
|
2682
|
-
}
|
|
2683
|
-
return { textures: newMap };
|
|
2684
|
-
});
|
|
2685
|
-
};
|
|
2686
|
-
const remove = (key) => {
|
|
2687
|
-
set((state) => {
|
|
2688
|
-
const newMap = new Map(state.textures);
|
|
2689
|
-
newMap.delete(key);
|
|
2690
|
-
return { textures: newMap };
|
|
2691
|
-
});
|
|
2692
|
-
};
|
|
2693
|
-
const removeMultiple = (keys) => {
|
|
2694
|
-
set((state) => {
|
|
2695
|
-
const newMap = new Map(state.textures);
|
|
2696
|
-
for (const key of keys) newMap.delete(key);
|
|
2697
|
-
return { textures: newMap };
|
|
2698
|
-
});
|
|
2699
|
-
};
|
|
2700
|
-
const dispose = (key) => {
|
|
2701
|
-
const entry = getState().textures.get(key);
|
|
2702
|
-
if (entry) {
|
|
2703
|
-
const tex = getTextureValue(entry);
|
|
2704
|
-
tex?.dispose();
|
|
2705
|
-
}
|
|
2706
|
-
remove(key);
|
|
2707
|
-
};
|
|
2708
|
-
const disposeMultiple = (keys) => {
|
|
2709
|
-
const textures = getState().textures;
|
|
2710
|
-
for (const key of keys) {
|
|
2711
|
-
const entry = textures.get(key);
|
|
2712
|
-
if (entry) {
|
|
2713
|
-
const tex = getTextureValue(entry);
|
|
2714
|
-
tex?.dispose();
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
removeMultiple(keys);
|
|
2718
|
-
};
|
|
2719
|
-
const disposeAll = () => {
|
|
2720
|
-
const textures = getState().textures;
|
|
2721
|
-
for (const entry of textures.values()) {
|
|
2722
|
-
const tex = getTextureValue(entry);
|
|
2723
|
-
tex?.dispose();
|
|
2724
|
-
}
|
|
2725
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2726
|
-
};
|
|
2314
|
+
const setState = store.setState;
|
|
2315
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2727
2316
|
return {
|
|
2728
|
-
|
|
2729
|
-
get textures() {
|
|
2317
|
+
get all() {
|
|
2730
2318
|
return getState().textures;
|
|
2731
2319
|
},
|
|
2732
|
-
|
|
2733
|
-
|
|
2320
|
+
get(input) {
|
|
2321
|
+
if (typeof input === "string") return getOne(input);
|
|
2322
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2323
|
+
const out = {};
|
|
2324
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2325
|
+
return out;
|
|
2326
|
+
},
|
|
2734
2327
|
has: (key) => getState().textures.has(key),
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2328
|
+
add(keyOrRecord, texture) {
|
|
2329
|
+
setState((state) => {
|
|
2330
|
+
const textures = new Map(state.textures);
|
|
2331
|
+
if (typeof keyOrRecord === "string") {
|
|
2332
|
+
textures.set(keyOrRecord, texture);
|
|
2333
|
+
} else {
|
|
2334
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2335
|
+
}
|
|
2336
|
+
return { textures };
|
|
2337
|
+
});
|
|
2338
|
+
},
|
|
2339
|
+
dispose(key, options) {
|
|
2340
|
+
const state = getState();
|
|
2341
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2342
|
+
if (refs > 0 && !options?.force) {
|
|
2343
|
+
console.warn(
|
|
2344
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2345
|
+
);
|
|
2346
|
+
return false;
|
|
2347
|
+
}
|
|
2348
|
+
state.textures.get(key)?.dispose();
|
|
2349
|
+
setState((s) => {
|
|
2350
|
+
const textures = new Map(s.textures);
|
|
2351
|
+
textures.delete(key);
|
|
2352
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2353
|
+
nextRefs.delete(key);
|
|
2354
|
+
return { textures, _textureRefs: nextRefs };
|
|
2355
|
+
});
|
|
2356
|
+
return true;
|
|
2357
|
+
},
|
|
2358
|
+
disposeAll() {
|
|
2359
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2360
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2361
|
+
}
|
|
2745
2362
|
};
|
|
2746
2363
|
}, [store]);
|
|
2364
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2365
|
+
const selected = useThree(subscribe);
|
|
2366
|
+
return selector ? selected : registry;
|
|
2747
2367
|
}
|
|
2748
2368
|
|
|
2749
|
-
function useRenderTarget(
|
|
2369
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2750
2370
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2751
2371
|
const size = useThree((s) => s.size);
|
|
2372
|
+
let width;
|
|
2373
|
+
let height;
|
|
2374
|
+
let opts;
|
|
2375
|
+
if (typeof widthOrOptions === "object") {
|
|
2376
|
+
opts = widthOrOptions;
|
|
2377
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2378
|
+
width = widthOrOptions;
|
|
2379
|
+
if (typeof heightOrOptions === "object") {
|
|
2380
|
+
height = widthOrOptions;
|
|
2381
|
+
opts = heightOrOptions;
|
|
2382
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2383
|
+
height = heightOrOptions;
|
|
2384
|
+
opts = options;
|
|
2385
|
+
} else {
|
|
2386
|
+
height = widthOrOptions;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2752
2389
|
return React.useMemo(() => {
|
|
2753
2390
|
const w = width ?? size.width;
|
|
2754
2391
|
const h = height ?? size.height;
|
|
2755
|
-
return new webgpu.RenderTarget(w, h,
|
|
2756
|
-
}, [width, height, size.width, size.height,
|
|
2392
|
+
return new webgpu.RenderTarget(w, h, opts);
|
|
2393
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2757
2394
|
}
|
|
2758
2395
|
|
|
2759
2396
|
function useStore() {
|
|
@@ -2781,7 +2418,7 @@ function addEffect(callback) {
|
|
|
2781
2418
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2782
2419
|
});
|
|
2783
2420
|
const id = `legacy_effect_${effectId++}`;
|
|
2784
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2421
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2785
2422
|
}
|
|
2786
2423
|
function addAfterEffect(callback) {
|
|
2787
2424
|
notifyDepreciated({
|
|
@@ -2790,7 +2427,7 @@ function addAfterEffect(callback) {
|
|
|
2790
2427
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2791
2428
|
});
|
|
2792
2429
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2793
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2430
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2794
2431
|
}
|
|
2795
2432
|
function addTail(callback) {
|
|
2796
2433
|
notifyDepreciated({
|
|
@@ -2798,16 +2435,16 @@ function addTail(callback) {
|
|
|
2798
2435
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2799
2436
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2800
2437
|
});
|
|
2801
|
-
return getScheduler().onIdle(callback);
|
|
2438
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2802
2439
|
}
|
|
2803
2440
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2804
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2441
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2805
2442
|
}
|
|
2806
|
-
function advance(timestamp
|
|
2807
|
-
getScheduler().step(timestamp);
|
|
2443
|
+
function advance(timestamp) {
|
|
2444
|
+
scheduler.getScheduler().step(timestamp);
|
|
2808
2445
|
}
|
|
2809
2446
|
|
|
2810
|
-
const version = "10.0.0-alpha.
|
|
2447
|
+
const version = "10.0.0-alpha.3";
|
|
2811
2448
|
const packageData = {
|
|
2812
2449
|
version: version};
|
|
2813
2450
|
|
|
@@ -14246,6 +13883,7 @@ function swapInstances() {
|
|
|
14246
13883
|
}
|
|
14247
13884
|
}
|
|
14248
13885
|
if (instance.isHidden) unhideInstance(instance);
|
|
13886
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14249
13887
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14250
13888
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14251
13889
|
}
|
|
@@ -14257,6 +13895,9 @@ function swapInstances() {
|
|
|
14257
13895
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14258
13896
|
instance.object.__r3f = instance;
|
|
14259
13897
|
setFiberRef(fiber, instance.object);
|
|
13898
|
+
delete instance.appliedOnce;
|
|
13899
|
+
instance.eventCount = 0;
|
|
13900
|
+
instance.handlers = {};
|
|
14260
13901
|
applyProps(instance.object, instance.props);
|
|
14261
13902
|
if (instance.props.attach) {
|
|
14262
13903
|
attach(parent, instance);
|
|
@@ -14330,8 +13971,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14330
13971
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14331
13972
|
if (isTailSibling) swapInstances();
|
|
14332
13973
|
},
|
|
14333
|
-
finalizeInitialChildren: () =>
|
|
14334
|
-
|
|
13974
|
+
finalizeInitialChildren: (instance) => {
|
|
13975
|
+
for (const prop in instance.props) {
|
|
13976
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13977
|
+
}
|
|
13978
|
+
return false;
|
|
13979
|
+
},
|
|
13980
|
+
commitMount(instance) {
|
|
13981
|
+
const resolved = {};
|
|
13982
|
+
for (const prop in instance.props) {
|
|
13983
|
+
const value = instance.props[prop];
|
|
13984
|
+
if (isFromRef(value)) {
|
|
13985
|
+
const ref = value[FROM_REF];
|
|
13986
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13987
|
+
}
|
|
13988
|
+
}
|
|
13989
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14335
13990
|
},
|
|
14336
13991
|
getPublicInstance: (instance) => instance?.object,
|
|
14337
13992
|
prepareForCommit: () => null,
|
|
@@ -14458,11 +14113,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14458
14113
|
cloneMutableTextInstance(textInstance) {
|
|
14459
14114
|
return textInstance;
|
|
14460
14115
|
},
|
|
14116
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14117
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14118
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14119
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14120
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14121
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14461
14122
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14462
|
-
throw new Error("Not implemented.");
|
|
14463
14123
|
},
|
|
14464
14124
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14465
|
-
throw new Error("Not implemented.");
|
|
14466
14125
|
},
|
|
14467
14126
|
// https://github.com/facebook/react/pull/32465
|
|
14468
14127
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14547,11 +14206,15 @@ function createRoot(canvas) {
|
|
|
14547
14206
|
const lastConfiguredProps = {};
|
|
14548
14207
|
let configured = false;
|
|
14549
14208
|
let pending = null;
|
|
14209
|
+
let rendererSetup = null;
|
|
14550
14210
|
return {
|
|
14551
14211
|
async configure(props = {}) {
|
|
14552
14212
|
let resolve;
|
|
14553
14213
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14554
14214
|
const {
|
|
14215
|
+
id: canvasId,
|
|
14216
|
+
primaryCanvas,
|
|
14217
|
+
scheduler: schedulerConfig,
|
|
14555
14218
|
gl: glConfig,
|
|
14556
14219
|
renderer: rendererConfig,
|
|
14557
14220
|
size: propsSize,
|
|
@@ -14559,10 +14222,6 @@ function createRoot(canvas) {
|
|
|
14559
14222
|
events,
|
|
14560
14223
|
onCreated: onCreatedCallback,
|
|
14561
14224
|
shadows = false,
|
|
14562
|
-
linear = false,
|
|
14563
|
-
flat = false,
|
|
14564
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14565
|
-
legacy = false,
|
|
14566
14225
|
orthographic = false,
|
|
14567
14226
|
frameloop = "always",
|
|
14568
14227
|
dpr = [1, 2],
|
|
@@ -14574,11 +14233,14 @@ function createRoot(canvas) {
|
|
|
14574
14233
|
onDropMissed,
|
|
14575
14234
|
autoUpdateFrustum = true,
|
|
14576
14235
|
occlusion = false,
|
|
14577
|
-
_sizeProps
|
|
14236
|
+
_sizeProps,
|
|
14237
|
+
forceEven
|
|
14578
14238
|
} = props;
|
|
14579
|
-
const
|
|
14239
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14240
|
+
let state = store.getState();
|
|
14580
14241
|
const defaultGPUProps = {
|
|
14581
|
-
canvas
|
|
14242
|
+
canvas,
|
|
14243
|
+
antialias: true
|
|
14582
14244
|
};
|
|
14583
14245
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14584
14246
|
throw new Error(
|
|
@@ -14590,14 +14252,63 @@ function createRoot(canvas) {
|
|
|
14590
14252
|
}
|
|
14591
14253
|
let renderer = state.internal.actualRenderer;
|
|
14592
14254
|
if (!state.internal.actualRenderer) {
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14255
|
+
if (!rendererSetup) {
|
|
14256
|
+
rendererSetup = (async () => {
|
|
14257
|
+
if (primaryCanvas) {
|
|
14258
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14259
|
+
renderer = primary.renderer;
|
|
14260
|
+
state.internal.actualRenderer = renderer;
|
|
14261
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14262
|
+
primary.store.setState((prev) => ({
|
|
14263
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14264
|
+
}));
|
|
14265
|
+
state.set((prev) => ({
|
|
14266
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14267
|
+
renderer,
|
|
14268
|
+
primaryStore: primary.store,
|
|
14269
|
+
internal: {
|
|
14270
|
+
...prev.internal,
|
|
14271
|
+
canvasTarget,
|
|
14272
|
+
isMultiCanvas: true,
|
|
14273
|
+
isSecondary: true,
|
|
14274
|
+
targetId: primaryCanvas
|
|
14275
|
+
}
|
|
14276
|
+
}));
|
|
14277
|
+
} else {
|
|
14278
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14279
|
+
if (!renderer.hasInitialized?.()) {
|
|
14280
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14281
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14282
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14283
|
+
canvas.width = size2.width * pixelRatio;
|
|
14284
|
+
canvas.height = size2.height * pixelRatio;
|
|
14285
|
+
}
|
|
14286
|
+
await renderer.init();
|
|
14287
|
+
}
|
|
14288
|
+
const backend = renderer.backend;
|
|
14289
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14290
|
+
state.internal.actualRenderer = renderer;
|
|
14291
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14292
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14293
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14294
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14295
|
+
state.set((prev) => ({
|
|
14296
|
+
internal: {
|
|
14297
|
+
...prev.internal,
|
|
14298
|
+
canvasTarget,
|
|
14299
|
+
unregisterPrimary
|
|
14300
|
+
}
|
|
14301
|
+
}));
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
14304
|
+
})().catch((err) => {
|
|
14305
|
+
rendererSetup = null;
|
|
14306
|
+
throw err;
|
|
14307
|
+
});
|
|
14596
14308
|
}
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
state.internal.actualRenderer
|
|
14600
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14309
|
+
await rendererSetup;
|
|
14310
|
+
state = store.getState();
|
|
14311
|
+
renderer = state.internal.actualRenderer;
|
|
14601
14312
|
}
|
|
14602
14313
|
let raycaster = state.raycaster;
|
|
14603
14314
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -14606,6 +14317,7 @@ function createRoot(canvas) {
|
|
|
14606
14317
|
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14607
14318
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14608
14319
|
}
|
|
14320
|
+
let tempCamera = state.camera;
|
|
14609
14321
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14610
14322
|
lastCamera = cameraOptions;
|
|
14611
14323
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14625,6 +14337,7 @@ function createRoot(canvas) {
|
|
|
14625
14337
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14626
14338
|
}
|
|
14627
14339
|
state.set({ camera });
|
|
14340
|
+
tempCamera = camera;
|
|
14628
14341
|
raycaster.camera = camera;
|
|
14629
14342
|
}
|
|
14630
14343
|
if (!state.scene) {
|
|
@@ -14642,7 +14355,7 @@ function createRoot(canvas) {
|
|
|
14642
14355
|
rootScene: scene,
|
|
14643
14356
|
internal: { ...prev.internal, container: scene }
|
|
14644
14357
|
}));
|
|
14645
|
-
const camera =
|
|
14358
|
+
const camera = tempCamera;
|
|
14646
14359
|
if (camera && !camera.parent) scene.add(camera);
|
|
14647
14360
|
}
|
|
14648
14361
|
if (events && !state.events.handlers) {
|
|
@@ -14659,6 +14372,9 @@ function createRoot(canvas) {
|
|
|
14659
14372
|
if (_sizeProps !== void 0) {
|
|
14660
14373
|
state.set({ _sizeProps });
|
|
14661
14374
|
}
|
|
14375
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14376
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14377
|
+
}
|
|
14662
14378
|
const size = computeInitialSize(canvas, propsSize);
|
|
14663
14379
|
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14664
14380
|
const wasImperative = state._sizeImperative;
|
|
@@ -14685,10 +14401,10 @@ function createRoot(canvas) {
|
|
|
14685
14401
|
lastConfiguredProps.performance = performance;
|
|
14686
14402
|
}
|
|
14687
14403
|
if (!state.xr) {
|
|
14688
|
-
const handleXRFrame = (timestamp,
|
|
14404
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14689
14405
|
const state2 = store.getState();
|
|
14690
14406
|
if (state2.frameloop === "never") return;
|
|
14691
|
-
advance(timestamp
|
|
14407
|
+
advance(timestamp);
|
|
14692
14408
|
};
|
|
14693
14409
|
const actualRenderer = state.internal.actualRenderer;
|
|
14694
14410
|
const handleSessionChange = () => {
|
|
@@ -14700,16 +14416,16 @@ function createRoot(canvas) {
|
|
|
14700
14416
|
};
|
|
14701
14417
|
const xr = {
|
|
14702
14418
|
connect() {
|
|
14703
|
-
const { gl, renderer: renderer2
|
|
14704
|
-
const
|
|
14705
|
-
|
|
14706
|
-
|
|
14419
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14420
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14421
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14422
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14707
14423
|
},
|
|
14708
14424
|
disconnect() {
|
|
14709
|
-
const { gl, renderer: renderer2
|
|
14710
|
-
const
|
|
14711
|
-
|
|
14712
|
-
|
|
14425
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14426
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14427
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14428
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14713
14429
|
}
|
|
14714
14430
|
};
|
|
14715
14431
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14721,15 +14437,22 @@ function createRoot(canvas) {
|
|
|
14721
14437
|
const oldType = renderer.shadowMap.type;
|
|
14722
14438
|
renderer.shadowMap.enabled = !!shadows;
|
|
14723
14439
|
if (is.boo(shadows)) {
|
|
14724
|
-
renderer.shadowMap.type = webgpu.
|
|
14440
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14725
14441
|
} else if (is.str(shadows)) {
|
|
14442
|
+
if (shadows === "soft") {
|
|
14443
|
+
notifyDepreciated({
|
|
14444
|
+
heading: 'shadows="soft" is deprecated',
|
|
14445
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14446
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14447
|
+
});
|
|
14448
|
+
}
|
|
14726
14449
|
const types = {
|
|
14727
14450
|
basic: webgpu.BasicShadowMap,
|
|
14728
14451
|
percentage: webgpu.PCFShadowMap,
|
|
14729
|
-
soft: webgpu.
|
|
14452
|
+
soft: webgpu.PCFShadowMap,
|
|
14730
14453
|
variance: webgpu.VSMShadowMap
|
|
14731
14454
|
};
|
|
14732
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14455
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14733
14456
|
} else if (is.obj(shadows)) {
|
|
14734
14457
|
Object.assign(renderer.shadowMap, shadows);
|
|
14735
14458
|
}
|
|
@@ -14737,28 +14460,70 @@ function createRoot(canvas) {
|
|
|
14737
14460
|
renderer.shadowMap.needsUpdate = true;
|
|
14738
14461
|
}
|
|
14739
14462
|
}
|
|
14463
|
+
if (!configured) {
|
|
14464
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14465
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14466
|
+
}
|
|
14740
14467
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14741
14468
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14742
14469
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14743
14470
|
}
|
|
14471
|
+
const r3fProps = ["textureColorSpace"];
|
|
14472
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14473
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14744
14474
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14745
|
-
|
|
14475
|
+
const glProps = {};
|
|
14476
|
+
for (const key in glConfig) {
|
|
14477
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14478
|
+
}
|
|
14479
|
+
applyProps(renderer, glProps);
|
|
14746
14480
|
}
|
|
14747
14481
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14748
14482
|
const currentRenderer = state.renderer;
|
|
14749
14483
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14750
|
-
|
|
14484
|
+
const rendererProps = {};
|
|
14485
|
+
for (const key in rendererConfig) {
|
|
14486
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14487
|
+
}
|
|
14488
|
+
applyProps(currentRenderer, rendererProps);
|
|
14751
14489
|
}
|
|
14752
14490
|
}
|
|
14753
|
-
const scheduler = getScheduler();
|
|
14491
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14754
14492
|
const rootId = state.internal.rootId;
|
|
14755
14493
|
if (!rootId) {
|
|
14756
|
-
const newRootId = scheduler.generateRootId();
|
|
14757
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14494
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14495
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14758
14496
|
getState: () => store.getState(),
|
|
14759
14497
|
onError: (err) => store.getState().setError(err)
|
|
14760
14498
|
});
|
|
14761
|
-
const
|
|
14499
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14500
|
+
() => {
|
|
14501
|
+
const state2 = store.getState();
|
|
14502
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14503
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14504
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14505
|
+
}
|
|
14506
|
+
},
|
|
14507
|
+
{
|
|
14508
|
+
id: `${newRootId}_canvasTarget`,
|
|
14509
|
+
rootId: newRootId,
|
|
14510
|
+
phase: "start",
|
|
14511
|
+
system: true
|
|
14512
|
+
}
|
|
14513
|
+
);
|
|
14514
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14515
|
+
() => {
|
|
14516
|
+
const state2 = store.getState();
|
|
14517
|
+
state2.events.flush?.();
|
|
14518
|
+
},
|
|
14519
|
+
{
|
|
14520
|
+
id: `${newRootId}_events`,
|
|
14521
|
+
rootId: newRootId,
|
|
14522
|
+
phase: "input",
|
|
14523
|
+
system: true
|
|
14524
|
+
}
|
|
14525
|
+
);
|
|
14526
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14762
14527
|
() => {
|
|
14763
14528
|
const state2 = store.getState();
|
|
14764
14529
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14768,11 +14533,11 @@ function createRoot(canvas) {
|
|
|
14768
14533
|
{
|
|
14769
14534
|
id: `${newRootId}_frustum`,
|
|
14770
14535
|
rootId: newRootId,
|
|
14771
|
-
|
|
14536
|
+
before: "render",
|
|
14772
14537
|
system: true
|
|
14773
14538
|
}
|
|
14774
14539
|
);
|
|
14775
|
-
const unregisterVisibility = scheduler.register(
|
|
14540
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14776
14541
|
() => {
|
|
14777
14542
|
const state2 = store.getState();
|
|
14778
14543
|
checkVisibility(state2);
|
|
@@ -14780,30 +14545,34 @@ function createRoot(canvas) {
|
|
|
14780
14545
|
{
|
|
14781
14546
|
id: `${newRootId}_visibility`,
|
|
14782
14547
|
rootId: newRootId,
|
|
14783
|
-
|
|
14548
|
+
before: "render",
|
|
14784
14549
|
system: true,
|
|
14785
14550
|
after: `${newRootId}_frustum`
|
|
14786
14551
|
}
|
|
14787
14552
|
);
|
|
14788
|
-
const unregisterRender = scheduler.register(
|
|
14553
|
+
const unregisterRender = scheduler$1.register(
|
|
14789
14554
|
() => {
|
|
14790
14555
|
const state2 = store.getState();
|
|
14791
14556
|
const renderer2 = state2.internal.actualRenderer;
|
|
14792
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14557
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14793
14558
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14794
14559
|
try {
|
|
14795
|
-
if (state2.
|
|
14560
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14796
14561
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14797
14562
|
} catch (error) {
|
|
14798
14563
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14799
14564
|
}
|
|
14800
14565
|
},
|
|
14801
14566
|
{
|
|
14802
|
-
|
|
14567
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14568
|
+
id: canvasId || `${newRootId}_render`,
|
|
14803
14569
|
rootId: newRootId,
|
|
14804
14570
|
phase: "render",
|
|
14805
|
-
system: true
|
|
14571
|
+
system: true,
|
|
14806
14572
|
// Internal flag: this is a system job, not user-controlled
|
|
14573
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14574
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14575
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14807
14576
|
}
|
|
14808
14577
|
);
|
|
14809
14578
|
state.set((state2) => ({
|
|
@@ -14812,15 +14581,17 @@ function createRoot(canvas) {
|
|
|
14812
14581
|
rootId: newRootId,
|
|
14813
14582
|
unregisterRoot: () => {
|
|
14814
14583
|
unregisterRoot();
|
|
14584
|
+
unregisterCanvasTarget();
|
|
14585
|
+
unregisterEventsFlush();
|
|
14815
14586
|
unregisterFrustum();
|
|
14816
14587
|
unregisterVisibility();
|
|
14817
14588
|
unregisterRender();
|
|
14818
14589
|
},
|
|
14819
|
-
scheduler
|
|
14590
|
+
scheduler: scheduler$1
|
|
14820
14591
|
}
|
|
14821
14592
|
}));
|
|
14822
14593
|
}
|
|
14823
|
-
scheduler.frameloop = frameloop;
|
|
14594
|
+
scheduler$1.frameloop = frameloop;
|
|
14824
14595
|
onCreated = onCreatedCallback;
|
|
14825
14596
|
configured = true;
|
|
14826
14597
|
resolve();
|
|
@@ -14870,15 +14641,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14870
14641
|
const renderer = state.internal.actualRenderer;
|
|
14871
14642
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14872
14643
|
if (unregisterRoot) unregisterRoot();
|
|
14644
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14645
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14646
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14647
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14873
14648
|
state.events.disconnect?.();
|
|
14874
14649
|
cleanupHelperGroup(root.store);
|
|
14875
|
-
renderer
|
|
14876
|
-
|
|
14877
|
-
|
|
14650
|
+
if (state.isLegacy && renderer) {
|
|
14651
|
+
;
|
|
14652
|
+
renderer.renderLists?.dispose?.();
|
|
14653
|
+
renderer.forceContextLoss?.();
|
|
14654
|
+
}
|
|
14655
|
+
if (!state.internal.isSecondary) {
|
|
14656
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14657
|
+
}
|
|
14878
14658
|
dispose(state.scene);
|
|
14879
14659
|
_roots.delete(canvas);
|
|
14880
14660
|
if (callback) callback(canvas);
|
|
14881
|
-
} catch
|
|
14661
|
+
} catch {
|
|
14882
14662
|
}
|
|
14883
14663
|
}, 500);
|
|
14884
14664
|
}
|
|
@@ -14886,36 +14666,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14886
14666
|
}
|
|
14887
14667
|
}
|
|
14888
14668
|
function createPortal(children, container, state) {
|
|
14889
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14669
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14890
14670
|
}
|
|
14891
|
-
function
|
|
14671
|
+
function Portal({ children, container, state }) {
|
|
14892
14672
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14893
|
-
const [resolvedContainer,
|
|
14673
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14894
14674
|
if (isRef(container)) return container.current ?? null;
|
|
14895
14675
|
return container;
|
|
14896
14676
|
});
|
|
14677
|
+
const setResolvedContainer = React.useCallback(
|
|
14678
|
+
(newContainer) => {
|
|
14679
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14680
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14681
|
+
},
|
|
14682
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14683
|
+
);
|
|
14897
14684
|
React.useMemo(() => {
|
|
14898
|
-
if (isRef(container)) {
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
const updated = container.current;
|
|
14903
|
-
if (updated && updated !== resolvedContainer) {
|
|
14904
|
-
setResolvedContainer(updated);
|
|
14905
|
-
}
|
|
14906
|
-
});
|
|
14907
|
-
} else if (current !== resolvedContainer) {
|
|
14908
|
-
setResolvedContainer(current);
|
|
14909
|
-
}
|
|
14910
|
-
} else if (container !== resolvedContainer) {
|
|
14911
|
-
setResolvedContainer(container);
|
|
14685
|
+
if (isRef(container) && !container.current) {
|
|
14686
|
+
return queueMicrotask(() => {
|
|
14687
|
+
setResolvedContainer(container.current);
|
|
14688
|
+
});
|
|
14912
14689
|
}
|
|
14913
|
-
|
|
14690
|
+
setResolvedContainer(container);
|
|
14691
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14914
14692
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14915
14693
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14916
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14694
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14917
14695
|
}
|
|
14918
|
-
function
|
|
14696
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14919
14697
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14920
14698
|
const previousRoot = useStore();
|
|
14921
14699
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14936,12 +14714,14 @@ function Portal({ state = {}, children, container }) {
|
|
|
14936
14714
|
};
|
|
14937
14715
|
}, [portalScene, container, injectScene]);
|
|
14938
14716
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14717
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14939
14718
|
let viewport = void 0;
|
|
14940
|
-
if (injectState.camera && size) {
|
|
14719
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14941
14720
|
const camera = injectState.camera;
|
|
14942
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14943
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14721
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14722
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14944
14723
|
}
|
|
14724
|
+
const set = injectState.set;
|
|
14945
14725
|
return {
|
|
14946
14726
|
// The intersect consists of the previous root state
|
|
14947
14727
|
...rootState,
|
|
@@ -14957,10 +14737,10 @@ function Portal({ state = {}, children, container }) {
|
|
|
14957
14737
|
previousRoot,
|
|
14958
14738
|
// Events, size and viewport can be overridden by the inject layer
|
|
14959
14739
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14960
|
-
size:
|
|
14740
|
+
size: resolvedSize,
|
|
14961
14741
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14962
14742
|
// Layers are allowed to override events
|
|
14963
|
-
setEvents: (events2) =>
|
|
14743
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
14964
14744
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
14965
14745
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
14966
14746
|
};
|
|
@@ -14969,9 +14749,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14969
14749
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14970
14750
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14971
14751
|
onMutate(previousRoot.getState());
|
|
14972
|
-
previousRoot.subscribe(onMutate);
|
|
14973
14752
|
return store;
|
|
14974
14753
|
}, [previousRoot, container]);
|
|
14754
|
+
useIsomorphicLayoutEffect(() => {
|
|
14755
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14756
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14757
|
+
return unsubscribe;
|
|
14758
|
+
}, [previousRoot, usePortalStore]);
|
|
14975
14759
|
return (
|
|
14976
14760
|
// @ts-ignore, reconciler types are not maintained
|
|
14977
14761
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -14985,21 +14769,72 @@ function flushSync(fn) {
|
|
|
14985
14769
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14986
14770
|
}
|
|
14987
14771
|
|
|
14772
|
+
function parseBackground(background) {
|
|
14773
|
+
if (!background) return null;
|
|
14774
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14775
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14776
|
+
return {
|
|
14777
|
+
...rest,
|
|
14778
|
+
preset,
|
|
14779
|
+
files: envMap || files,
|
|
14780
|
+
backgroundFiles: backgroundMap,
|
|
14781
|
+
background: true
|
|
14782
|
+
};
|
|
14783
|
+
}
|
|
14784
|
+
if (typeof background === "number") {
|
|
14785
|
+
return { color: background, background: true };
|
|
14786
|
+
}
|
|
14787
|
+
if (typeof background === "string") {
|
|
14788
|
+
if (background in presetsObj) {
|
|
14789
|
+
return { preset: background, background: true };
|
|
14790
|
+
}
|
|
14791
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14792
|
+
return { files: background, background: true };
|
|
14793
|
+
}
|
|
14794
|
+
return { color: background, background: true };
|
|
14795
|
+
}
|
|
14796
|
+
if (background.isColor) {
|
|
14797
|
+
return { color: background, background: true };
|
|
14798
|
+
}
|
|
14799
|
+
return null;
|
|
14800
|
+
}
|
|
14801
|
+
|
|
14802
|
+
function parseRendererConfig(rendererProp) {
|
|
14803
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14804
|
+
if (isRendererConfig) {
|
|
14805
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14806
|
+
return {
|
|
14807
|
+
primaryCanvas: pc,
|
|
14808
|
+
scheduler: sc,
|
|
14809
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14810
|
+
};
|
|
14811
|
+
}
|
|
14812
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14813
|
+
}
|
|
14814
|
+
|
|
14815
|
+
function clearHmrCaches(store) {
|
|
14816
|
+
store.setState((state) => ({
|
|
14817
|
+
nodes: {},
|
|
14818
|
+
uniforms: {},
|
|
14819
|
+
buffers: {},
|
|
14820
|
+
gpuStorage: {},
|
|
14821
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14822
|
+
}));
|
|
14823
|
+
}
|
|
14824
|
+
|
|
14988
14825
|
function CanvasImpl({
|
|
14989
14826
|
ref,
|
|
14990
14827
|
children,
|
|
14991
14828
|
fallback,
|
|
14992
14829
|
resize,
|
|
14993
14830
|
style,
|
|
14831
|
+
id,
|
|
14994
14832
|
gl,
|
|
14995
|
-
renderer,
|
|
14833
|
+
renderer: rendererProp,
|
|
14996
14834
|
events = createPointerEvents,
|
|
14997
14835
|
eventSource,
|
|
14998
14836
|
eventPrefix,
|
|
14999
14837
|
shadows,
|
|
15000
|
-
linear,
|
|
15001
|
-
flat,
|
|
15002
|
-
legacy,
|
|
15003
14838
|
orthographic,
|
|
15004
14839
|
frameloop,
|
|
15005
14840
|
dpr,
|
|
@@ -15007,6 +14842,8 @@ function CanvasImpl({
|
|
|
15007
14842
|
raycaster,
|
|
15008
14843
|
camera,
|
|
15009
14844
|
scene,
|
|
14845
|
+
autoUpdateFrustum,
|
|
14846
|
+
occlusion,
|
|
15010
14847
|
onPointerMissed,
|
|
15011
14848
|
onDragOverMissed,
|
|
15012
14849
|
onDropMissed,
|
|
@@ -15014,10 +14851,14 @@ function CanvasImpl({
|
|
|
15014
14851
|
hmr,
|
|
15015
14852
|
width,
|
|
15016
14853
|
height,
|
|
14854
|
+
background,
|
|
14855
|
+
forceEven,
|
|
15017
14856
|
...props
|
|
15018
14857
|
}) {
|
|
14858
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15019
14859
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15020
14860
|
const Bridge = useBridge();
|
|
14861
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15021
14862
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15022
14863
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15023
14864
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15034,15 +14875,20 @@ function CanvasImpl({
|
|
|
15034
14875
|
};
|
|
15035
14876
|
}, [resize, hasInitialSizeRef.current]);
|
|
15036
14877
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15037
|
-
const effectiveSize = React__namespace.useMemo(
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
14878
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14879
|
+
let w = width ?? containerRect.width;
|
|
14880
|
+
let h = height ?? containerRect.height;
|
|
14881
|
+
if (forceEven) {
|
|
14882
|
+
w = Math.ceil(w / 2) * 2;
|
|
14883
|
+
h = Math.ceil(h / 2) * 2;
|
|
14884
|
+
}
|
|
14885
|
+
return {
|
|
14886
|
+
width: w,
|
|
14887
|
+
height: h,
|
|
15041
14888
|
top: containerRect.top,
|
|
15042
14889
|
left: containerRect.left
|
|
15043
|
-
}
|
|
15044
|
-
|
|
15045
|
-
);
|
|
14890
|
+
};
|
|
14891
|
+
}, [width, height, containerRect, forceEven]);
|
|
15046
14892
|
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15047
14893
|
hasInitialSizeRef.current = true;
|
|
15048
14894
|
}
|
|
@@ -15054,6 +14900,7 @@ function CanvasImpl({
|
|
|
15054
14900
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15055
14901
|
const [block, setBlock] = React__namespace.useState(false);
|
|
15056
14902
|
const [error, setError] = React__namespace.useState(false);
|
|
14903
|
+
const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
|
|
15057
14904
|
if (block) throw block;
|
|
15058
14905
|
if (error) throw error;
|
|
15059
14906
|
const root = React__namespace.useRef(null);
|
|
@@ -15061,6 +14908,7 @@ function CanvasImpl({
|
|
|
15061
14908
|
const unsubscribeErrorRef = React__namespace.useRef(null);
|
|
15062
14909
|
useIsomorphicLayoutEffect(() => {
|
|
15063
14910
|
effectActiveRef.current = true;
|
|
14911
|
+
if (fallbackVisible) return;
|
|
15064
14912
|
const canvas = canvasRef.current;
|
|
15065
14913
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15066
14914
|
if (!root.current) {
|
|
@@ -15081,24 +14929,27 @@ function CanvasImpl({
|
|
|
15081
14929
|
}
|
|
15082
14930
|
async function run() {
|
|
15083
14931
|
if (!effectActiveRef.current || !root.current) return;
|
|
15084
|
-
await root.current.configure({
|
|
14932
|
+
const configured = await root.current.configure({
|
|
14933
|
+
id,
|
|
14934
|
+
primaryCanvas,
|
|
14935
|
+
scheduler,
|
|
15085
14936
|
gl,
|
|
15086
14937
|
renderer,
|
|
15087
14938
|
scene,
|
|
15088
14939
|
events,
|
|
15089
14940
|
shadows,
|
|
15090
|
-
linear,
|
|
15091
|
-
flat,
|
|
15092
|
-
legacy,
|
|
15093
14941
|
orthographic,
|
|
15094
14942
|
frameloop,
|
|
15095
14943
|
dpr,
|
|
15096
14944
|
performance,
|
|
15097
14945
|
raycaster,
|
|
15098
14946
|
camera,
|
|
14947
|
+
autoUpdateFrustum,
|
|
14948
|
+
occlusion,
|
|
15099
14949
|
size: effectiveSize,
|
|
15100
14950
|
// Store size props for reset functionality
|
|
15101
14951
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14952
|
+
forceEven,
|
|
15102
14953
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15103
14954
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15104
14955
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15119,10 +14970,25 @@ function CanvasImpl({
|
|
|
15119
14970
|
}
|
|
15120
14971
|
onCreated?.(state);
|
|
15121
14972
|
}
|
|
15122
|
-
})
|
|
15123
|
-
|
|
14973
|
+
}).then(
|
|
14974
|
+
() => true,
|
|
14975
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
14976
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
14977
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
14978
|
+
(setupError) => {
|
|
14979
|
+
if (effectActiveRef.current) {
|
|
14980
|
+
if (fallback != null) setFallbackVisible(true);
|
|
14981
|
+
else setError(setupError);
|
|
14982
|
+
}
|
|
14983
|
+
return false;
|
|
14984
|
+
}
|
|
14985
|
+
);
|
|
14986
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15124
14987
|
root.current.render(
|
|
15125
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14988
|
+
/* @__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: [
|
|
14989
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
14990
|
+
children ?? null
|
|
14991
|
+
] }) }) })
|
|
15126
14992
|
);
|
|
15127
14993
|
}
|
|
15128
14994
|
run();
|
|
@@ -15149,20 +15015,15 @@ function CanvasImpl({
|
|
|
15149
15015
|
const canvas = canvasRef.current;
|
|
15150
15016
|
if (!canvas) return;
|
|
15151
15017
|
const handleHMR = () => {
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
rootEntry
|
|
15155
|
-
|
|
15156
|
-
uniforms: {},
|
|
15157
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15158
|
-
}));
|
|
15159
|
-
}
|
|
15018
|
+
queueMicrotask(() => {
|
|
15019
|
+
const rootEntry = _roots.get(canvas);
|
|
15020
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15021
|
+
});
|
|
15160
15022
|
};
|
|
15161
15023
|
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) {
|
|
15162
15024
|
const hot = undefined;
|
|
15163
15025
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15164
|
-
return () => hot.
|
|
15165
|
-
});
|
|
15026
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15166
15027
|
}
|
|
15167
15028
|
if (typeof module !== "undefined" && module.hot) {
|
|
15168
15029
|
const hot = module.hot;
|
|
@@ -15185,7 +15046,20 @@ function CanvasImpl({
|
|
|
15185
15046
|
...style
|
|
15186
15047
|
},
|
|
15187
15048
|
...props,
|
|
15188
|
-
children:
|
|
15049
|
+
children: fallbackVisible ? (
|
|
15050
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15051
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15052
|
+
fallback
|
|
15053
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
15054
|
+
"canvas",
|
|
15055
|
+
{
|
|
15056
|
+
ref: canvasRef,
|
|
15057
|
+
id,
|
|
15058
|
+
className: "r3f-canvas",
|
|
15059
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15060
|
+
children: fallback
|
|
15061
|
+
}
|
|
15062
|
+
) })
|
|
15189
15063
|
}
|
|
15190
15064
|
);
|
|
15191
15065
|
}
|
|
@@ -15258,6 +15132,48 @@ let ScopedStore = _ScopedStore;
|
|
|
15258
15132
|
function createScopedStore(data) {
|
|
15259
15133
|
return new ScopedStore(data);
|
|
15260
15134
|
}
|
|
15135
|
+
function createLazyCreatorState(state) {
|
|
15136
|
+
let _uniforms = null;
|
|
15137
|
+
let _nodes = null;
|
|
15138
|
+
let _buffers = null;
|
|
15139
|
+
let _gpuStorage = null;
|
|
15140
|
+
return Object.create(state, {
|
|
15141
|
+
uniforms: {
|
|
15142
|
+
get() {
|
|
15143
|
+
return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
|
|
15144
|
+
}
|
|
15145
|
+
},
|
|
15146
|
+
nodes: {
|
|
15147
|
+
get() {
|
|
15148
|
+
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15149
|
+
}
|
|
15150
|
+
},
|
|
15151
|
+
buffers: {
|
|
15152
|
+
get() {
|
|
15153
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15154
|
+
}
|
|
15155
|
+
},
|
|
15156
|
+
gpuStorage: {
|
|
15157
|
+
get() {
|
|
15158
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15159
|
+
}
|
|
15160
|
+
}
|
|
15161
|
+
});
|
|
15162
|
+
}
|
|
15163
|
+
|
|
15164
|
+
function resolvePrimary(local) {
|
|
15165
|
+
return local.getState().primaryStore ?? local;
|
|
15166
|
+
}
|
|
15167
|
+
function usePrimaryStore() {
|
|
15168
|
+
const local = useStore();
|
|
15169
|
+
const primary = local.getState().primaryStore;
|
|
15170
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15171
|
+
}
|
|
15172
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15173
|
+
const local = useStore();
|
|
15174
|
+
const primary = resolvePrimary(local);
|
|
15175
|
+
return primary(selector, equalityFn);
|
|
15176
|
+
}
|
|
15261
15177
|
|
|
15262
15178
|
function addTexture(set, key, value) {
|
|
15263
15179
|
set((state) => {
|
|
@@ -15298,6 +15214,27 @@ function createTextureOperations(set) {
|
|
|
15298
15214
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15299
15215
|
};
|
|
15300
15216
|
}
|
|
15217
|
+
function extractTSLValue(value) {
|
|
15218
|
+
if (value === null || value === void 0) return value;
|
|
15219
|
+
if (typeof value !== "object") return value;
|
|
15220
|
+
const node = value;
|
|
15221
|
+
if (!node.isNode) return value;
|
|
15222
|
+
if (node.isConstNode) {
|
|
15223
|
+
return node.value;
|
|
15224
|
+
}
|
|
15225
|
+
if ("value" in node) {
|
|
15226
|
+
let extractedValue = node.value;
|
|
15227
|
+
if (typeof node.traverse === "function") {
|
|
15228
|
+
node.traverse((n) => {
|
|
15229
|
+
if (n.isConstNode) {
|
|
15230
|
+
extractedValue = n.value;
|
|
15231
|
+
}
|
|
15232
|
+
});
|
|
15233
|
+
}
|
|
15234
|
+
return extractedValue;
|
|
15235
|
+
}
|
|
15236
|
+
return value;
|
|
15237
|
+
}
|
|
15301
15238
|
function vectorize(inObject) {
|
|
15302
15239
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15303
15240
|
if (typeof inObject === "string") {
|
|
@@ -15310,9 +15247,16 @@ function vectorize(inObject) {
|
|
|
15310
15247
|
}
|
|
15311
15248
|
if (typeof inObject !== "object") return inObject;
|
|
15312
15249
|
const obj = inObject;
|
|
15250
|
+
if (obj.isNode) {
|
|
15251
|
+
return extractTSLValue(inObject);
|
|
15252
|
+
}
|
|
15313
15253
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15314
15254
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15315
15255
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15256
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15257
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15258
|
+
return new webgpu.Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15259
|
+
}
|
|
15316
15260
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15317
15261
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15318
15262
|
return new webgpu.Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15336,7 +15280,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15336
15280
|
|
|
15337
15281
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15338
15282
|
function useUniforms(creatorOrScope, scope) {
|
|
15339
|
-
const store =
|
|
15283
|
+
const store = usePrimaryStore();
|
|
15340
15284
|
const removeUniforms2 = React.useCallback(
|
|
15341
15285
|
(names, targetScope) => {
|
|
15342
15286
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15375,17 +15319,14 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15375
15319
|
const rebuildUniforms = React.useCallback(
|
|
15376
15320
|
(targetScope) => {
|
|
15377
15321
|
store.setState((state) => {
|
|
15378
|
-
let newUniforms =
|
|
15322
|
+
let newUniforms = {};
|
|
15379
15323
|
if (targetScope && targetScope !== "root") {
|
|
15380
15324
|
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15381
15325
|
newUniforms = rest;
|
|
15382
15326
|
} else if (targetScope === "root") {
|
|
15383
|
-
newUniforms = {};
|
|
15384
15327
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15385
15328
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15386
15329
|
}
|
|
15387
|
-
} else {
|
|
15388
|
-
newUniforms = {};
|
|
15389
15330
|
}
|
|
15390
15331
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15391
15332
|
});
|
|
@@ -15393,20 +15334,26 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15393
15334
|
[store]
|
|
15394
15335
|
);
|
|
15395
15336
|
const inputForMemoization = React.useMemo(() => {
|
|
15337
|
+
let raw = creatorOrScope;
|
|
15396
15338
|
if (is.fun(creatorOrScope)) {
|
|
15397
|
-
const
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15339
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15340
|
+
raw = creatorOrScope(wrappedState);
|
|
15341
|
+
}
|
|
15342
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15343
|
+
const normalized = {};
|
|
15344
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15345
|
+
normalized[key] = vectorize(value);
|
|
15346
|
+
}
|
|
15347
|
+
return normalized;
|
|
15404
15348
|
}
|
|
15405
|
-
return
|
|
15349
|
+
return raw;
|
|
15406
15350
|
}, [creatorOrScope, store]);
|
|
15407
15351
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15408
15352
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15409
|
-
const storeUniforms =
|
|
15353
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15354
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15355
|
+
const readerDep = isReader ? storeUniforms : null;
|
|
15356
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15410
15357
|
const uniforms = React.useMemo(() => {
|
|
15411
15358
|
if (memoizedInput === void 0) {
|
|
15412
15359
|
return storeUniforms;
|
|
@@ -15461,28 +15408,20 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15461
15408
|
}
|
|
15462
15409
|
}
|
|
15463
15410
|
return result;
|
|
15464
|
-
}, [
|
|
15465
|
-
store,
|
|
15466
|
-
memoizedInput,
|
|
15467
|
-
scope,
|
|
15468
|
-
// Only include storeUniforms in deps for reader modes to enable reactivity
|
|
15469
|
-
isReader ? storeUniforms : null
|
|
15470
|
-
]);
|
|
15411
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15471
15412
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15472
15413
|
}
|
|
15473
15414
|
function rebuildAllUniforms(store, scope) {
|
|
15415
|
+
store = store.getState().primaryStore ?? store;
|
|
15474
15416
|
store.setState((state) => {
|
|
15475
|
-
let newUniforms =
|
|
15417
|
+
let newUniforms = {};
|
|
15476
15418
|
if (scope && scope !== "root") {
|
|
15477
15419
|
const { [scope]: _, ...rest } = state.uniforms;
|
|
15478
15420
|
newUniforms = rest;
|
|
15479
15421
|
} else if (scope === "root") {
|
|
15480
|
-
newUniforms = {};
|
|
15481
15422
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15482
15423
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15483
15424
|
}
|
|
15484
|
-
} else {
|
|
15485
|
-
newUniforms = {};
|
|
15486
15425
|
}
|
|
15487
15426
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15488
15427
|
});
|
|
@@ -15550,15 +15489,17 @@ function isSameThreeType(a, b) {
|
|
|
15550
15489
|
}
|
|
15551
15490
|
|
|
15552
15491
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15492
|
+
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
15553
15493
|
function useUniform(name, value) {
|
|
15554
15494
|
const store = useStore();
|
|
15495
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15555
15496
|
return React.useMemo(() => {
|
|
15556
15497
|
const state = store.getState();
|
|
15557
15498
|
const set = store.setState;
|
|
15558
15499
|
const existing = state.uniforms[name];
|
|
15559
15500
|
if (existing && isUniformNode(existing)) {
|
|
15560
|
-
if (value !== void 0) {
|
|
15561
|
-
existing.value = value;
|
|
15501
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15502
|
+
existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
|
|
15562
15503
|
}
|
|
15563
15504
|
return existing;
|
|
15564
15505
|
}
|
|
@@ -15567,7 +15508,24 @@ function useUniform(name, value) {
|
|
|
15567
15508
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15568
15509
|
);
|
|
15569
15510
|
}
|
|
15570
|
-
|
|
15511
|
+
if (isUniformNode(value)) {
|
|
15512
|
+
const node2 = value;
|
|
15513
|
+
if (typeof node2.setName === "function") {
|
|
15514
|
+
node2.setName(name);
|
|
15515
|
+
}
|
|
15516
|
+
set((s) => ({
|
|
15517
|
+
uniforms: { ...s.uniforms, [name]: node2 }
|
|
15518
|
+
}));
|
|
15519
|
+
return node2;
|
|
15520
|
+
}
|
|
15521
|
+
let node;
|
|
15522
|
+
if (isTSLNode$1(value)) {
|
|
15523
|
+
node = tsl.uniform(value);
|
|
15524
|
+
} else if (typeof value === "string") {
|
|
15525
|
+
node = tsl.uniform(new webgpu.Color(value));
|
|
15526
|
+
} else {
|
|
15527
|
+
node = tsl.uniform(value);
|
|
15528
|
+
}
|
|
15571
15529
|
if (typeof node.setName === "function") {
|
|
15572
15530
|
node.setName(name);
|
|
15573
15531
|
}
|
|
@@ -15578,12 +15536,12 @@ function useUniform(name, value) {
|
|
|
15578
15536
|
}
|
|
15579
15537
|
}));
|
|
15580
15538
|
return node;
|
|
15581
|
-
}, [store, name]);
|
|
15539
|
+
}, [store, name, hmrVersion]);
|
|
15582
15540
|
}
|
|
15583
15541
|
|
|
15584
15542
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15585
15543
|
function useNodes(creatorOrScope, scope) {
|
|
15586
|
-
const store =
|
|
15544
|
+
const store = usePrimaryStore();
|
|
15587
15545
|
const removeNodes2 = React.useCallback(
|
|
15588
15546
|
(names, targetScope) => {
|
|
15589
15547
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15640,8 +15598,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15640
15598
|
[store]
|
|
15641
15599
|
);
|
|
15642
15600
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15643
|
-
const storeNodes =
|
|
15644
|
-
const hmrVersion =
|
|
15601
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15602
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15603
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15604
|
+
const readerDep = isReader ? storeNodes : null;
|
|
15605
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15645
15606
|
const nodes = React.useMemo(() => {
|
|
15646
15607
|
if (creatorOrScope === void 0) {
|
|
15647
15608
|
return storeNodes;
|
|
@@ -15654,11 +15615,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15654
15615
|
const state = store.getState();
|
|
15655
15616
|
const set = store.setState;
|
|
15656
15617
|
const creator = creatorOrScope;
|
|
15657
|
-
const wrappedState =
|
|
15658
|
-
...state,
|
|
15659
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15660
|
-
nodes: createScopedStore(state.nodes)
|
|
15661
|
-
};
|
|
15618
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15662
15619
|
const created = creator(wrappedState);
|
|
15663
15620
|
const result = {};
|
|
15664
15621
|
let hasNewNodes = false;
|
|
@@ -15668,7 +15625,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15668
15625
|
if (currentScope[name]) {
|
|
15669
15626
|
result[name] = currentScope[name];
|
|
15670
15627
|
} else {
|
|
15671
|
-
|
|
15628
|
+
node.setName?.(`${scope}.${name}`);
|
|
15672
15629
|
result[name] = node;
|
|
15673
15630
|
hasNewNodes = true;
|
|
15674
15631
|
}
|
|
@@ -15688,7 +15645,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15688
15645
|
if (existing && isTSLNode(existing)) {
|
|
15689
15646
|
result[name] = existing;
|
|
15690
15647
|
} else {
|
|
15691
|
-
|
|
15648
|
+
node.setName?.(name);
|
|
15692
15649
|
result[name] = node;
|
|
15693
15650
|
hasNewNodes = true;
|
|
15694
15651
|
}
|
|
@@ -15697,18 +15654,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15697
15654
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15698
15655
|
}
|
|
15699
15656
|
return result;
|
|
15700
|
-
}, [
|
|
15701
|
-
store,
|
|
15702
|
-
typeof creatorOrScope === "string" ? creatorOrScope : scope,
|
|
15703
|
-
// Only include storeNodes in deps for reader modes to enable reactivity
|
|
15704
|
-
// Creator mode intentionally excludes it to avoid re-running creator on unrelated changes
|
|
15705
|
-
isReader ? storeNodes : null,
|
|
15706
|
-
// Include hmrVersion for creator modes to allow rebuildNodes() to bust the cache
|
|
15707
|
-
isReader ? null : hmrVersion
|
|
15708
|
-
]);
|
|
15657
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15709
15658
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15710
15659
|
}
|
|
15711
15660
|
function rebuildAllNodes(store, scope) {
|
|
15661
|
+
store = store.getState().primaryStore ?? store;
|
|
15712
15662
|
store.setState((state) => {
|
|
15713
15663
|
let newNodes = state.nodes;
|
|
15714
15664
|
if (scope && scope !== "root") {
|
|
@@ -15753,22 +15703,365 @@ function clearRootNodes(set) {
|
|
|
15753
15703
|
});
|
|
15754
15704
|
}
|
|
15755
15705
|
function useLocalNodes(creator) {
|
|
15756
|
-
const store =
|
|
15757
|
-
const uniforms =
|
|
15758
|
-
const nodes =
|
|
15759
|
-
const textures =
|
|
15706
|
+
const store = usePrimaryStore();
|
|
15707
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15708
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15709
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15710
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15760
15711
|
return React.useMemo(() => {
|
|
15761
|
-
const
|
|
15762
|
-
const wrappedState = {
|
|
15763
|
-
...state,
|
|
15764
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15765
|
-
nodes: createScopedStore(state.nodes)
|
|
15766
|
-
};
|
|
15712
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15767
15713
|
return creator(wrappedState);
|
|
15768
|
-
}, [store, creator, uniforms, nodes, textures]);
|
|
15714
|
+
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
15715
|
+
}
|
|
15716
|
+
|
|
15717
|
+
const isBufferLike = (value) => {
|
|
15718
|
+
if (value === null || typeof value !== "object") return false;
|
|
15719
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15720
|
+
if ("isBufferAttribute" in value) return true;
|
|
15721
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15722
|
+
return false;
|
|
15723
|
+
};
|
|
15724
|
+
const disposeBuffer = (buffer) => {
|
|
15725
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15726
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15727
|
+
buffer.dispose();
|
|
15728
|
+
}
|
|
15729
|
+
};
|
|
15730
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15731
|
+
const store = usePrimaryStore();
|
|
15732
|
+
const removeBuffers = React.useCallback(
|
|
15733
|
+
(names, targetScope) => {
|
|
15734
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15735
|
+
store.setState((state) => {
|
|
15736
|
+
if (targetScope) {
|
|
15737
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15738
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15739
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15740
|
+
}
|
|
15741
|
+
const buffers2 = { ...state.buffers };
|
|
15742
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15743
|
+
return { buffers: buffers2 };
|
|
15744
|
+
});
|
|
15745
|
+
},
|
|
15746
|
+
[store]
|
|
15747
|
+
);
|
|
15748
|
+
const clearBuffers = React.useCallback(
|
|
15749
|
+
(targetScope) => {
|
|
15750
|
+
store.setState((state) => {
|
|
15751
|
+
if (targetScope && targetScope !== "root") {
|
|
15752
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15753
|
+
return { buffers: rest };
|
|
15754
|
+
}
|
|
15755
|
+
if (targetScope === "root") {
|
|
15756
|
+
const buffers2 = {};
|
|
15757
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15758
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15759
|
+
}
|
|
15760
|
+
return { buffers: buffers2 };
|
|
15761
|
+
}
|
|
15762
|
+
return { buffers: {} };
|
|
15763
|
+
});
|
|
15764
|
+
},
|
|
15765
|
+
[store]
|
|
15766
|
+
);
|
|
15767
|
+
const rebuildBuffers = React.useCallback(
|
|
15768
|
+
(targetScope) => {
|
|
15769
|
+
store.setState((state) => {
|
|
15770
|
+
let newBuffers = state.buffers;
|
|
15771
|
+
if (targetScope && targetScope !== "root") {
|
|
15772
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15773
|
+
newBuffers = rest;
|
|
15774
|
+
} else if (targetScope === "root") {
|
|
15775
|
+
newBuffers = {};
|
|
15776
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15777
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15778
|
+
}
|
|
15779
|
+
} else {
|
|
15780
|
+
newBuffers = {};
|
|
15781
|
+
}
|
|
15782
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15783
|
+
});
|
|
15784
|
+
},
|
|
15785
|
+
[store]
|
|
15786
|
+
);
|
|
15787
|
+
const disposeBuffers = React.useCallback(
|
|
15788
|
+
(names, targetScope) => {
|
|
15789
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15790
|
+
const state = store.getState();
|
|
15791
|
+
for (const name of nameArray) {
|
|
15792
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15793
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15794
|
+
disposeBuffer(buffer);
|
|
15795
|
+
}
|
|
15796
|
+
}
|
|
15797
|
+
removeBuffers(names, targetScope);
|
|
15798
|
+
},
|
|
15799
|
+
[store, removeBuffers]
|
|
15800
|
+
);
|
|
15801
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15802
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15803
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15804
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15805
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15806
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15807
|
+
const buffers = React.useMemo(() => {
|
|
15808
|
+
if (creatorOrScope === void 0) {
|
|
15809
|
+
return storeBuffers;
|
|
15810
|
+
}
|
|
15811
|
+
if (typeof creatorOrScope === "string") {
|
|
15812
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15813
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15814
|
+
return {};
|
|
15815
|
+
}
|
|
15816
|
+
const state = store.getState();
|
|
15817
|
+
const set = store.setState;
|
|
15818
|
+
const creator = creatorOrScope;
|
|
15819
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15820
|
+
const created = creator(wrappedState);
|
|
15821
|
+
const result = {};
|
|
15822
|
+
let hasNewBuffers = false;
|
|
15823
|
+
if (scope) {
|
|
15824
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15825
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15826
|
+
if (currentScope[name]) {
|
|
15827
|
+
result[name] = currentScope[name];
|
|
15828
|
+
} else {
|
|
15829
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15830
|
+
buffer.setName(`${scope}.${name}`);
|
|
15831
|
+
}
|
|
15832
|
+
result[name] = buffer;
|
|
15833
|
+
hasNewBuffers = true;
|
|
15834
|
+
}
|
|
15835
|
+
}
|
|
15836
|
+
if (hasNewBuffers) {
|
|
15837
|
+
set((s) => ({
|
|
15838
|
+
buffers: {
|
|
15839
|
+
...s.buffers,
|
|
15840
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15841
|
+
}
|
|
15842
|
+
}));
|
|
15843
|
+
}
|
|
15844
|
+
return result;
|
|
15845
|
+
}
|
|
15846
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15847
|
+
const existing = state.buffers[name];
|
|
15848
|
+
if (existing && isBufferLike(existing)) {
|
|
15849
|
+
result[name] = existing;
|
|
15850
|
+
} else {
|
|
15851
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15852
|
+
buffer.setName(name);
|
|
15853
|
+
}
|
|
15854
|
+
result[name] = buffer;
|
|
15855
|
+
hasNewBuffers = true;
|
|
15856
|
+
}
|
|
15857
|
+
}
|
|
15858
|
+
if (hasNewBuffers) {
|
|
15859
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15860
|
+
}
|
|
15861
|
+
return result;
|
|
15862
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15863
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15864
|
+
}
|
|
15865
|
+
function rebuildAllBuffers(store, scope) {
|
|
15866
|
+
store = store.getState().primaryStore ?? store;
|
|
15867
|
+
store.setState((state) => {
|
|
15868
|
+
let newBuffers = state.buffers;
|
|
15869
|
+
if (scope && scope !== "root") {
|
|
15870
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15871
|
+
newBuffers = rest;
|
|
15872
|
+
} else if (scope === "root") {
|
|
15873
|
+
newBuffers = {};
|
|
15874
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15875
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15876
|
+
}
|
|
15877
|
+
} else {
|
|
15878
|
+
newBuffers = {};
|
|
15879
|
+
}
|
|
15880
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15881
|
+
});
|
|
15882
|
+
}
|
|
15883
|
+
|
|
15884
|
+
const isStorageLike = (value) => {
|
|
15885
|
+
if (value === null || typeof value !== "object") return false;
|
|
15886
|
+
if ("isTexture" in value) return true;
|
|
15887
|
+
if ("isData3DTexture" in value) return true;
|
|
15888
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15889
|
+
return false;
|
|
15890
|
+
};
|
|
15891
|
+
const disposeStorage = (storage) => {
|
|
15892
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15893
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15894
|
+
storage.dispose();
|
|
15895
|
+
}
|
|
15896
|
+
};
|
|
15897
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15898
|
+
const store = usePrimaryStore();
|
|
15899
|
+
const removeStorage = React.useCallback(
|
|
15900
|
+
(names, targetScope) => {
|
|
15901
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15902
|
+
store.setState((state) => {
|
|
15903
|
+
if (targetScope) {
|
|
15904
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15905
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15906
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15907
|
+
}
|
|
15908
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15909
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15910
|
+
return { gpuStorage: gpuStorage2 };
|
|
15911
|
+
});
|
|
15912
|
+
},
|
|
15913
|
+
[store]
|
|
15914
|
+
);
|
|
15915
|
+
const clearStorage = React.useCallback(
|
|
15916
|
+
(targetScope) => {
|
|
15917
|
+
store.setState((state) => {
|
|
15918
|
+
if (targetScope && targetScope !== "root") {
|
|
15919
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15920
|
+
return { gpuStorage: rest };
|
|
15921
|
+
}
|
|
15922
|
+
if (targetScope === "root") {
|
|
15923
|
+
const gpuStorage2 = {};
|
|
15924
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15925
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15926
|
+
}
|
|
15927
|
+
return { gpuStorage: gpuStorage2 };
|
|
15928
|
+
}
|
|
15929
|
+
return { gpuStorage: {} };
|
|
15930
|
+
});
|
|
15931
|
+
},
|
|
15932
|
+
[store]
|
|
15933
|
+
);
|
|
15934
|
+
const rebuildStorage = React.useCallback(
|
|
15935
|
+
(targetScope) => {
|
|
15936
|
+
store.setState((state) => {
|
|
15937
|
+
let newStorage = state.gpuStorage;
|
|
15938
|
+
if (targetScope && targetScope !== "root") {
|
|
15939
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15940
|
+
newStorage = rest;
|
|
15941
|
+
} else if (targetScope === "root") {
|
|
15942
|
+
newStorage = {};
|
|
15943
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15944
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15945
|
+
}
|
|
15946
|
+
} else {
|
|
15947
|
+
newStorage = {};
|
|
15948
|
+
}
|
|
15949
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15950
|
+
});
|
|
15951
|
+
},
|
|
15952
|
+
[store]
|
|
15953
|
+
);
|
|
15954
|
+
const disposeStorageFn = React.useCallback(
|
|
15955
|
+
(names, targetScope) => {
|
|
15956
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15957
|
+
const state = store.getState();
|
|
15958
|
+
for (const name of nameArray) {
|
|
15959
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15960
|
+
if (storage && isStorageLike(storage)) {
|
|
15961
|
+
disposeStorage(storage);
|
|
15962
|
+
}
|
|
15963
|
+
}
|
|
15964
|
+
removeStorage(names, targetScope);
|
|
15965
|
+
},
|
|
15966
|
+
[store, removeStorage]
|
|
15967
|
+
);
|
|
15968
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15969
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15970
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15971
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15972
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15973
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15974
|
+
const gpuStorage = React.useMemo(() => {
|
|
15975
|
+
if (creatorOrScope === void 0) {
|
|
15976
|
+
return storeStorage;
|
|
15977
|
+
}
|
|
15978
|
+
if (typeof creatorOrScope === "string") {
|
|
15979
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15980
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15981
|
+
return {};
|
|
15982
|
+
}
|
|
15983
|
+
const state = store.getState();
|
|
15984
|
+
const set = store.setState;
|
|
15985
|
+
const creator = creatorOrScope;
|
|
15986
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15987
|
+
const created = creator(wrappedState);
|
|
15988
|
+
const result = {};
|
|
15989
|
+
let hasNewStorage = false;
|
|
15990
|
+
if (scope) {
|
|
15991
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15992
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15993
|
+
if (currentScope[name]) {
|
|
15994
|
+
result[name] = currentScope[name];
|
|
15995
|
+
} else {
|
|
15996
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15997
|
+
storage.setName(`${scope}.${name}`);
|
|
15998
|
+
}
|
|
15999
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
16000
|
+
storage.name = `${scope}.${name}`;
|
|
16001
|
+
}
|
|
16002
|
+
result[name] = storage;
|
|
16003
|
+
hasNewStorage = true;
|
|
16004
|
+
}
|
|
16005
|
+
}
|
|
16006
|
+
if (hasNewStorage) {
|
|
16007
|
+
set((s) => ({
|
|
16008
|
+
gpuStorage: {
|
|
16009
|
+
...s.gpuStorage,
|
|
16010
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
16011
|
+
}
|
|
16012
|
+
}));
|
|
16013
|
+
}
|
|
16014
|
+
return result;
|
|
16015
|
+
}
|
|
16016
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
16017
|
+
const existing = state.gpuStorage[name];
|
|
16018
|
+
if (existing && isStorageLike(existing)) {
|
|
16019
|
+
result[name] = existing;
|
|
16020
|
+
} else {
|
|
16021
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
16022
|
+
storage.setName(name);
|
|
16023
|
+
}
|
|
16024
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
16025
|
+
storage.name = name;
|
|
16026
|
+
}
|
|
16027
|
+
result[name] = storage;
|
|
16028
|
+
hasNewStorage = true;
|
|
16029
|
+
}
|
|
16030
|
+
}
|
|
16031
|
+
if (hasNewStorage) {
|
|
16032
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
16033
|
+
}
|
|
16034
|
+
return result;
|
|
16035
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
16036
|
+
return {
|
|
16037
|
+
...gpuStorage,
|
|
16038
|
+
removeStorage,
|
|
16039
|
+
clearStorage,
|
|
16040
|
+
rebuildStorage,
|
|
16041
|
+
disposeStorage: disposeStorageFn
|
|
16042
|
+
};
|
|
16043
|
+
}
|
|
16044
|
+
function rebuildAllStorage(store, scope) {
|
|
16045
|
+
store = store.getState().primaryStore ?? store;
|
|
16046
|
+
store.setState((state) => {
|
|
16047
|
+
let newStorage = state.gpuStorage;
|
|
16048
|
+
if (scope && scope !== "root") {
|
|
16049
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
16050
|
+
newStorage = rest;
|
|
16051
|
+
} else if (scope === "root") {
|
|
16052
|
+
newStorage = {};
|
|
16053
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16054
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
16055
|
+
}
|
|
16056
|
+
} else {
|
|
16057
|
+
newStorage = {};
|
|
16058
|
+
}
|
|
16059
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
16060
|
+
});
|
|
15769
16061
|
}
|
|
15770
16062
|
|
|
15771
|
-
|
|
16063
|
+
const RenderPipelineCtor = webgpu.RenderPipeline ?? webgpu.PostProcessing;
|
|
16064
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15772
16065
|
const store = useStore();
|
|
15773
16066
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15774
16067
|
const callbacksRanRef = React.useRef(false);
|
|
@@ -15787,7 +16080,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15787
16080
|
}, [store]);
|
|
15788
16081
|
const reset = React.useCallback(() => {
|
|
15789
16082
|
store.setState({
|
|
15790
|
-
|
|
16083
|
+
renderPipeline: null,
|
|
15791
16084
|
passes: {}
|
|
15792
16085
|
});
|
|
15793
16086
|
callbacksRanRef.current = false;
|
|
@@ -15800,18 +16093,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15800
16093
|
}, []);
|
|
15801
16094
|
React.useLayoutEffect(() => {
|
|
15802
16095
|
if (isLegacy) {
|
|
15803
|
-
throw new Error("
|
|
16096
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15804
16097
|
}
|
|
15805
16098
|
if (!renderer || !scene || !camera) return;
|
|
15806
16099
|
const state = store.getState();
|
|
15807
16100
|
const set = store.setState;
|
|
15808
16101
|
try {
|
|
15809
|
-
let
|
|
16102
|
+
let pipeline = state.renderPipeline;
|
|
15810
16103
|
let currentPasses = { ...state.passes };
|
|
15811
|
-
let
|
|
15812
|
-
if (!
|
|
15813
|
-
|
|
15814
|
-
|
|
16104
|
+
let justCreatedPipeline = false;
|
|
16105
|
+
if (!pipeline) {
|
|
16106
|
+
pipeline = new RenderPipelineCtor(renderer);
|
|
16107
|
+
justCreatedPipeline = true;
|
|
15815
16108
|
}
|
|
15816
16109
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
15817
16110
|
let scenePass;
|
|
@@ -15822,9 +16115,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15822
16115
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
15823
16116
|
}
|
|
15824
16117
|
currentPasses.scenePass = scenePass;
|
|
15825
|
-
if (!
|
|
15826
|
-
set({
|
|
15827
|
-
const shouldRunCallbacks =
|
|
16118
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
16119
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
16120
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
15828
16121
|
if (shouldRunCallbacks) {
|
|
15829
16122
|
if (setupCBRef.current) {
|
|
15830
16123
|
const freshState = store.getState();
|
|
@@ -15845,33 +16138,41 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15845
16138
|
callbacksRanRef.current = true;
|
|
15846
16139
|
}
|
|
15847
16140
|
} catch (error) {
|
|
15848
|
-
console.error("[
|
|
16141
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15849
16142
|
}
|
|
15850
16143
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15851
16144
|
const passes = useThree((s) => s.passes);
|
|
15852
|
-
const
|
|
16145
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15853
16146
|
return {
|
|
15854
16147
|
passes,
|
|
15855
|
-
|
|
16148
|
+
renderPipeline,
|
|
15856
16149
|
clearPasses,
|
|
15857
16150
|
reset,
|
|
15858
16151
|
rebuild,
|
|
15859
|
-
// isReady indicates if
|
|
15860
|
-
isReady:
|
|
16152
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16153
|
+
isReady: renderPipeline !== null
|
|
15861
16154
|
};
|
|
15862
16155
|
}
|
|
15863
16156
|
|
|
15864
16157
|
extend(THREE);
|
|
15865
16158
|
|
|
16159
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
16160
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15866
16161
|
exports.Block = Block;
|
|
15867
16162
|
exports.Canvas = Canvas;
|
|
16163
|
+
exports.Environment = Environment;
|
|
16164
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
16165
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
16166
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15868
16167
|
exports.ErrorBoundary = ErrorBoundary;
|
|
16168
|
+
exports.FROM_REF = FROM_REF;
|
|
15869
16169
|
exports.IsObject = IsObject;
|
|
16170
|
+
exports.ONCE = ONCE;
|
|
16171
|
+
exports.Portal = Portal;
|
|
15870
16172
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15871
16173
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15872
16174
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15873
16175
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15874
|
-
exports.Scheduler = Scheduler;
|
|
15875
16176
|
exports.Texture = Texture;
|
|
15876
16177
|
exports._roots = _roots;
|
|
15877
16178
|
exports.act = act;
|
|
@@ -15902,35 +16203,49 @@ exports.events = createPointerEvents;
|
|
|
15902
16203
|
exports.extend = extend;
|
|
15903
16204
|
exports.findInitialRoot = findInitialRoot;
|
|
15904
16205
|
exports.flushSync = flushSync;
|
|
16206
|
+
exports.fromRef = fromRef;
|
|
15905
16207
|
exports.getInstanceProps = getInstanceProps;
|
|
16208
|
+
exports.getPrimary = getPrimary;
|
|
16209
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15906
16210
|
exports.getRootState = getRootState;
|
|
15907
|
-
exports.getScheduler = getScheduler;
|
|
15908
16211
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15909
16212
|
exports.hasConstructor = hasConstructor;
|
|
16213
|
+
exports.hasPrimary = hasPrimary;
|
|
15910
16214
|
exports.invalidate = invalidate;
|
|
15911
16215
|
exports.invalidateInstance = invalidateInstance;
|
|
15912
16216
|
exports.is = is;
|
|
15913
16217
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15914
16218
|
exports.isCopyable = isCopyable;
|
|
16219
|
+
exports.isFromRef = isFromRef;
|
|
15915
16220
|
exports.isObject3D = isObject3D;
|
|
16221
|
+
exports.isOnce = isOnce;
|
|
15916
16222
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15917
16223
|
exports.isRef = isRef;
|
|
15918
16224
|
exports.isRenderer = isRenderer;
|
|
15919
16225
|
exports.isTexture = isTexture;
|
|
15920
16226
|
exports.isVectorLike = isVectorLike;
|
|
16227
|
+
exports.once = once;
|
|
15921
16228
|
exports.prepare = prepare;
|
|
16229
|
+
exports.presetsObj = presetsObj;
|
|
16230
|
+
exports.rebuildAllBuffers = rebuildAllBuffers;
|
|
15922
16231
|
exports.rebuildAllNodes = rebuildAllNodes;
|
|
16232
|
+
exports.rebuildAllStorage = rebuildAllStorage;
|
|
15923
16233
|
exports.rebuildAllUniforms = rebuildAllUniforms;
|
|
15924
16234
|
exports.reconciler = reconciler;
|
|
16235
|
+
exports.registerPrimary = registerPrimary;
|
|
15925
16236
|
exports.removeInteractivity = removeInteractivity;
|
|
15926
16237
|
exports.removeNodes = removeNodes;
|
|
15927
16238
|
exports.removeUniforms = removeUniforms;
|
|
15928
16239
|
exports.resolve = resolve;
|
|
15929
16240
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
16241
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15930
16242
|
exports.updateCamera = updateCamera;
|
|
15931
16243
|
exports.updateFrustum = updateFrustum;
|
|
15932
16244
|
exports.useBridge = useBridge;
|
|
16245
|
+
exports.useBuffers = useBuffers;
|
|
16246
|
+
exports.useEnvironment = useEnvironment;
|
|
15933
16247
|
exports.useFrame = useFrame;
|
|
16248
|
+
exports.useGPUStorage = useGPUStorage;
|
|
15934
16249
|
exports.useGraph = useGraph;
|
|
15935
16250
|
exports.useInstanceHandle = useInstanceHandle;
|
|
15936
16251
|
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
|
@@ -15938,7 +16253,7 @@ exports.useLoader = useLoader;
|
|
|
15938
16253
|
exports.useLocalNodes = useLocalNodes;
|
|
15939
16254
|
exports.useMutableCallback = useMutableCallback;
|
|
15940
16255
|
exports.useNodes = useNodes;
|
|
15941
|
-
exports.
|
|
16256
|
+
exports.useRenderPipeline = useRenderPipeline;
|
|
15942
16257
|
exports.useRenderTarget = useRenderTarget;
|
|
15943
16258
|
exports.useStore = useStore;
|
|
15944
16259
|
exports.useTexture = useTexture;
|
|
@@ -15946,3 +16261,4 @@ exports.useTextures = useTextures;
|
|
|
15946
16261
|
exports.useThree = useThree;
|
|
15947
16262
|
exports.useUniform = useUniform;
|
|
15948
16263
|
exports.useUniforms = useUniforms;
|
|
16264
|
+
exports.waitForPrimary = waitForPrimary;
|