@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.164c7be
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/index.cjs +1163 -1375
- package/dist/index.d.cts +1887 -1710
- package/dist/index.d.mts +1887 -1710
- package/dist/index.d.ts +1887 -1710
- package/dist/index.mjs +1124 -1354
- package/dist/legacy.cjs +1129 -1365
- package/dist/legacy.d.cts +1888 -1711
- package/dist/legacy.d.mts +1888 -1711
- package/dist/legacy.d.ts +1888 -1711
- package/dist/legacy.mjs +1090 -1344
- package/dist/webgpu/index.cjs +1720 -1486
- package/dist/webgpu/index.d.cts +2064 -1734
- package/dist/webgpu/index.d.mts +2064 -1734
- package/dist/webgpu/index.d.ts +2064 -1734
- package/dist/webgpu/index.mjs +1678 -1466
- 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 = {
|
|
@@ -246,7 +636,8 @@ function prepare(target, root, type, props) {
|
|
|
246
636
|
object,
|
|
247
637
|
eventCount: 0,
|
|
248
638
|
handlers: {},
|
|
249
|
-
isHidden: false
|
|
639
|
+
isHidden: false,
|
|
640
|
+
deferredRefs: []
|
|
250
641
|
};
|
|
251
642
|
if (object) object.__r3f = instance;
|
|
252
643
|
}
|
|
@@ -295,7 +686,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
295
686
|
let occlusionSetupPromise = null;
|
|
296
687
|
function enableOcclusion(store) {
|
|
297
688
|
const state = store.getState();
|
|
298
|
-
const { internal, renderer
|
|
689
|
+
const { internal, renderer } = state;
|
|
299
690
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
300
691
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
301
692
|
if (!hasOcclusionSupport) {
|
|
@@ -458,6 +849,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
458
849
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
459
850
|
}
|
|
460
851
|
|
|
852
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
853
|
+
function fromRef(ref) {
|
|
854
|
+
return { [FROM_REF]: ref };
|
|
855
|
+
}
|
|
856
|
+
function isFromRef(value) {
|
|
857
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
861
|
+
function once(...args) {
|
|
862
|
+
return { [ONCE]: args.length ? args : true };
|
|
863
|
+
}
|
|
864
|
+
function isOnce(value) {
|
|
865
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
866
|
+
}
|
|
867
|
+
|
|
461
868
|
const RESERVED_PROPS = [
|
|
462
869
|
"children",
|
|
463
870
|
"key",
|
|
@@ -528,7 +935,7 @@ function getMemoizedPrototype(root) {
|
|
|
528
935
|
ctor = new root.constructor();
|
|
529
936
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
530
937
|
}
|
|
531
|
-
} catch
|
|
938
|
+
} catch {
|
|
532
939
|
}
|
|
533
940
|
return ctor;
|
|
534
941
|
}
|
|
@@ -574,6 +981,25 @@ function applyProps(object, props) {
|
|
|
574
981
|
continue;
|
|
575
982
|
}
|
|
576
983
|
if (value === void 0) continue;
|
|
984
|
+
if (isFromRef(value)) {
|
|
985
|
+
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (isOnce(value)) {
|
|
989
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
990
|
+
if (instance) {
|
|
991
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
992
|
+
instance.appliedOnce.add(prop);
|
|
993
|
+
}
|
|
994
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
995
|
+
const args = value[ONCE];
|
|
996
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
997
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
998
|
+
} else if (args !== true && args.length > 0) {
|
|
999
|
+
targetRoot[targetKey] = args[0];
|
|
1000
|
+
}
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
577
1003
|
let { root, key, target } = resolve(object, prop);
|
|
578
1004
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
579
1005
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -596,7 +1022,10 @@ function applyProps(object, props) {
|
|
|
596
1022
|
else target.set(value);
|
|
597
1023
|
} else {
|
|
598
1024
|
root[key] = value;
|
|
599
|
-
if (
|
|
1025
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1026
|
+
root.needsUpdate = true;
|
|
1027
|
+
}
|
|
1028
|
+
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
1029
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
601
1030
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
602
1031
|
}
|
|
@@ -629,38 +1058,60 @@ function applyProps(object, props) {
|
|
|
629
1058
|
return object;
|
|
630
1059
|
}
|
|
631
1060
|
|
|
1061
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1062
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1063
|
+
function getPointerState(internal, pointerId) {
|
|
1064
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1065
|
+
if (!state) {
|
|
1066
|
+
state = {
|
|
1067
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1068
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1069
|
+
initialClick: [0, 0],
|
|
1070
|
+
initialHits: []
|
|
1071
|
+
};
|
|
1072
|
+
internal.pointerMap.set(pointerId, state);
|
|
1073
|
+
}
|
|
1074
|
+
return state;
|
|
1075
|
+
}
|
|
1076
|
+
function getPointerId(event) {
|
|
1077
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1078
|
+
}
|
|
632
1079
|
function makeId(event) {
|
|
633
1080
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
634
1081
|
}
|
|
635
|
-
function releaseInternalPointerCapture(
|
|
636
|
-
const
|
|
1082
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1083
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1084
|
+
if (!pointerState) return;
|
|
1085
|
+
const captureData = pointerState.captured.get(obj);
|
|
637
1086
|
if (captureData) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
capturedMap.delete(pointerId);
|
|
641
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
642
|
-
}
|
|
1087
|
+
pointerState.captured.delete(obj);
|
|
1088
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
643
1089
|
}
|
|
644
1090
|
}
|
|
645
1091
|
function removeInteractivity(store, object) {
|
|
646
1092
|
const { internal } = store.getState();
|
|
647
1093
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1094
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1095
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1096
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1097
|
+
if (value.eventObject === object || value.object === object) {
|
|
1098
|
+
pointerState.hovered.delete(key);
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
if (pointerState.captured.has(object)) {
|
|
1102
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
652
1103
|
}
|
|
653
|
-
}
|
|
654
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
655
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
656
|
-
});
|
|
1104
|
+
}
|
|
657
1105
|
unregisterVisibility(store, object);
|
|
658
1106
|
}
|
|
659
1107
|
function createEvents(store) {
|
|
660
|
-
function calculateDistance(event) {
|
|
1108
|
+
function calculateDistance(event, pointerId) {
|
|
661
1109
|
const { internal } = store.getState();
|
|
662
|
-
const
|
|
663
|
-
|
|
1110
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1111
|
+
if (!pointerState) return 0;
|
|
1112
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1113
|
+
const dx = event.offsetX - initialX;
|
|
1114
|
+
const dy = event.offsetY - initialY;
|
|
664
1115
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
665
1116
|
}
|
|
666
1117
|
function filterPointerEvents(objects) {
|
|
@@ -696,6 +1147,15 @@ function createEvents(store) {
|
|
|
696
1147
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
697
1148
|
}
|
|
698
1149
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1150
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1151
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1152
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1153
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1154
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1155
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1156
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
699
1159
|
const aState = getRootState(a.object);
|
|
700
1160
|
const bState = getRootState(b.object);
|
|
701
1161
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -717,9 +1177,13 @@ function createEvents(store) {
|
|
|
717
1177
|
eventObject = eventObject.parent;
|
|
718
1178
|
}
|
|
719
1179
|
}
|
|
720
|
-
if ("pointerId" in event
|
|
721
|
-
|
|
722
|
-
|
|
1180
|
+
if ("pointerId" in event) {
|
|
1181
|
+
const pointerId = event.pointerId;
|
|
1182
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1183
|
+
if (pointerState?.captured.size) {
|
|
1184
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1185
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1186
|
+
}
|
|
723
1187
|
}
|
|
724
1188
|
}
|
|
725
1189
|
return intersections;
|
|
@@ -732,27 +1196,25 @@ function createEvents(store) {
|
|
|
732
1196
|
if (state) {
|
|
733
1197
|
const { raycaster, pointer, camera, internal } = state;
|
|
734
1198
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
735
|
-
const hasPointerCapture = (id) =>
|
|
1199
|
+
const hasPointerCapture = (id) => {
|
|
1200
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1201
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1202
|
+
};
|
|
736
1203
|
const setPointerCapture = (id) => {
|
|
737
1204
|
const captureData = { intersection: hit, target: event.target };
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
} else {
|
|
741
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
742
|
-
}
|
|
1205
|
+
const pointerState = getPointerState(internal, id);
|
|
1206
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
743
1207
|
event.target.setPointerCapture(id);
|
|
744
1208
|
};
|
|
745
1209
|
const releasePointerCapture = (id) => {
|
|
746
|
-
|
|
747
|
-
if (captures) {
|
|
748
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
749
|
-
}
|
|
1210
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
750
1211
|
};
|
|
751
1212
|
const extractEventProps = {};
|
|
752
1213
|
for (const prop in event) {
|
|
753
1214
|
const property = event[prop];
|
|
754
1215
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
755
1216
|
}
|
|
1217
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
756
1218
|
const raycastEvent = {
|
|
757
1219
|
...hit,
|
|
758
1220
|
...extractEventProps,
|
|
@@ -763,18 +1225,19 @@ function createEvents(store) {
|
|
|
763
1225
|
unprojectedPoint,
|
|
764
1226
|
ray: raycaster.ray,
|
|
765
1227
|
camera,
|
|
1228
|
+
pointerId: eventPointerId,
|
|
766
1229
|
// Hijack stopPropagation, which just sets a flag
|
|
767
1230
|
stopPropagation() {
|
|
768
|
-
const
|
|
1231
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
769
1232
|
if (
|
|
770
1233
|
// ...if this pointer hasn't been captured
|
|
771
|
-
!
|
|
772
|
-
|
|
1234
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1235
|
+
pointerState.captured.has(hit.eventObject)
|
|
773
1236
|
) {
|
|
774
1237
|
raycastEvent.stopped = localState.stopped = true;
|
|
775
|
-
if (
|
|
1238
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
776
1239
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
777
|
-
cancelPointer([...higher, hit]);
|
|
1240
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
778
1241
|
}
|
|
779
1242
|
}
|
|
780
1243
|
},
|
|
@@ -790,15 +1253,18 @@ function createEvents(store) {
|
|
|
790
1253
|
}
|
|
791
1254
|
return intersections;
|
|
792
1255
|
}
|
|
793
|
-
function cancelPointer(intersections) {
|
|
1256
|
+
function cancelPointer(intersections, pointerId) {
|
|
794
1257
|
const { internal } = store.getState();
|
|
795
|
-
|
|
1258
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1259
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1260
|
+
if (!pointerState) return;
|
|
1261
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
796
1262
|
if (!intersections.length || !intersections.find(
|
|
797
1263
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
798
1264
|
)) {
|
|
799
1265
|
const eventObject = hoveredObj.eventObject;
|
|
800
1266
|
const instance = eventObject.__r3f;
|
|
801
|
-
|
|
1267
|
+
pointerState.hovered.delete(hoveredId);
|
|
802
1268
|
if (instance?.eventCount) {
|
|
803
1269
|
const handlers = instance.handlers;
|
|
804
1270
|
const data = { ...hoveredObj, intersections };
|
|
@@ -827,41 +1293,118 @@ function createEvents(store) {
|
|
|
827
1293
|
instance?.handlers.onDropMissed?.(event);
|
|
828
1294
|
}
|
|
829
1295
|
}
|
|
1296
|
+
function cleanupPointer(pointerId) {
|
|
1297
|
+
const { internal } = store.getState();
|
|
1298
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1299
|
+
if (pointerState) {
|
|
1300
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1301
|
+
const eventObject = hoveredObj.eventObject;
|
|
1302
|
+
const instance = eventObject.__r3f;
|
|
1303
|
+
if (instance?.eventCount) {
|
|
1304
|
+
const handlers = instance.handlers;
|
|
1305
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1306
|
+
handlers.onPointerOut?.(data);
|
|
1307
|
+
handlers.onPointerLeave?.(data);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
internal.pointerMap.delete(pointerId);
|
|
1311
|
+
}
|
|
1312
|
+
internal.pointerDirty.delete(pointerId);
|
|
1313
|
+
}
|
|
1314
|
+
function processDeferredPointer(event, pointerId) {
|
|
1315
|
+
const state = store.getState();
|
|
1316
|
+
const { internal } = state;
|
|
1317
|
+
if (!state.events.enabled) return;
|
|
1318
|
+
const filter = filterPointerEvents;
|
|
1319
|
+
const hits = intersect(event, filter);
|
|
1320
|
+
cancelPointer(hits, pointerId);
|
|
1321
|
+
function onIntersect(data) {
|
|
1322
|
+
const eventObject = data.eventObject;
|
|
1323
|
+
const instance = eventObject.__r3f;
|
|
1324
|
+
if (!instance?.eventCount) return;
|
|
1325
|
+
const handlers = instance.handlers;
|
|
1326
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1327
|
+
const id = makeId(data);
|
|
1328
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1329
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1330
|
+
if (!hoveredItem) {
|
|
1331
|
+
pointerState.hovered.set(id, data);
|
|
1332
|
+
handlers.onPointerOver?.(data);
|
|
1333
|
+
handlers.onPointerEnter?.(data);
|
|
1334
|
+
} else if (hoveredItem.stopped) {
|
|
1335
|
+
data.stopPropagation();
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
handlers.onPointerMove?.(data);
|
|
1339
|
+
}
|
|
1340
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1341
|
+
}
|
|
830
1342
|
function handlePointer(name) {
|
|
831
1343
|
switch (name) {
|
|
832
1344
|
case "onPointerLeave":
|
|
833
|
-
case "onPointerCancel":
|
|
834
1345
|
case "onDragLeave":
|
|
835
1346
|
return () => cancelPointer([]);
|
|
1347
|
+
// Global cancel of these events
|
|
1348
|
+
case "onPointerCancel":
|
|
1349
|
+
return (event) => {
|
|
1350
|
+
const pointerId = getPointerId(event);
|
|
1351
|
+
cleanupPointer(pointerId);
|
|
1352
|
+
};
|
|
836
1353
|
case "onLostPointerCapture":
|
|
837
1354
|
return (event) => {
|
|
838
1355
|
const { internal } = store.getState();
|
|
839
|
-
|
|
1356
|
+
const pointerId = getPointerId(event);
|
|
1357
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1358
|
+
if (pointerState?.captured.size) {
|
|
840
1359
|
requestAnimationFrame(() => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1360
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1361
|
+
if (pointerState2?.captured.size) {
|
|
1362
|
+
pointerState2.captured.clear();
|
|
844
1363
|
}
|
|
1364
|
+
cancelPointer([], pointerId);
|
|
845
1365
|
});
|
|
846
1366
|
}
|
|
847
1367
|
};
|
|
848
1368
|
}
|
|
849
1369
|
return function handleEvent(event) {
|
|
850
1370
|
const state = store.getState();
|
|
851
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1371
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1372
|
+
const pointerId = getPointerId(event);
|
|
852
1373
|
internal.lastEvent.current = event;
|
|
853
|
-
if (!
|
|
1374
|
+
if (!events.enabled) return;
|
|
854
1375
|
const isPointerMove = name === "onPointerMove";
|
|
855
1376
|
const isDragOver = name === "onDragOver";
|
|
856
1377
|
const isDrop = name === "onDrop";
|
|
857
1378
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1379
|
+
const isPointerDown = name === "onPointerDown";
|
|
1380
|
+
const isPointerUp = name === "onPointerUp";
|
|
1381
|
+
const isWheel = name === "onWheel";
|
|
1382
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1383
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1384
|
+
events.compute?.(event, state);
|
|
1385
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1389
|
+
events.compute?.(event, state);
|
|
1390
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1394
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1395
|
+
internal.pointerDirty.delete(pointerId);
|
|
1396
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1397
|
+
}
|
|
858
1398
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
859
1399
|
const hits = intersect(event, filter);
|
|
860
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1400
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1401
|
+
if (isPointerDown) {
|
|
1402
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1403
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1404
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1405
|
+
}
|
|
1406
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1407
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
865
1408
|
if (isClickEvent && !hits.length) {
|
|
866
1409
|
if (delta <= 2) {
|
|
867
1410
|
pointerMissed(event, internal.interaction);
|
|
@@ -876,7 +1419,9 @@ function createEvents(store) {
|
|
|
876
1419
|
dropMissed(event, internal.interaction);
|
|
877
1420
|
if (onDropMissed) onDropMissed(event);
|
|
878
1421
|
}
|
|
879
|
-
if (isPointerMove || isDragOver)
|
|
1422
|
+
if (isPointerMove || isDragOver) {
|
|
1423
|
+
cancelPointer(hits, pointerId);
|
|
1424
|
+
}
|
|
880
1425
|
function onIntersect(data) {
|
|
881
1426
|
const eventObject = data.eventObject;
|
|
882
1427
|
const instance = eventObject.__r3f;
|
|
@@ -885,9 +1430,10 @@ function createEvents(store) {
|
|
|
885
1430
|
if (isPointerMove) {
|
|
886
1431
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
887
1432
|
const id = makeId(data);
|
|
888
|
-
const
|
|
1433
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1434
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
889
1435
|
if (!hoveredItem) {
|
|
890
|
-
|
|
1436
|
+
pointerState2.hovered.set(id, data);
|
|
891
1437
|
handlers.onPointerOver?.(data);
|
|
892
1438
|
handlers.onPointerEnter?.(data);
|
|
893
1439
|
} else if (hoveredItem.stopped) {
|
|
@@ -897,9 +1443,10 @@ function createEvents(store) {
|
|
|
897
1443
|
handlers.onPointerMove?.(data);
|
|
898
1444
|
} else if (isDragOver) {
|
|
899
1445
|
const id = makeId(data);
|
|
900
|
-
const
|
|
1446
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1447
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
901
1448
|
if (!hoveredItem) {
|
|
902
|
-
|
|
1449
|
+
pointerState2.hovered.set(id, data);
|
|
903
1450
|
handlers.onDragOverEnter?.(data);
|
|
904
1451
|
} else if (hoveredItem.stopped) {
|
|
905
1452
|
data.stopPropagation();
|
|
@@ -910,18 +1457,18 @@ function createEvents(store) {
|
|
|
910
1457
|
} else {
|
|
911
1458
|
const handler = handlers[name];
|
|
912
1459
|
if (handler) {
|
|
913
|
-
if (!isClickEvent ||
|
|
1460
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
914
1461
|
pointerMissed(
|
|
915
1462
|
event,
|
|
916
|
-
internal.interaction.filter((object) => !
|
|
1463
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
917
1464
|
);
|
|
918
1465
|
handler(data);
|
|
919
1466
|
}
|
|
920
1467
|
} else {
|
|
921
|
-
if (isClickEvent &&
|
|
1468
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
922
1469
|
pointerMissed(
|
|
923
1470
|
event,
|
|
924
|
-
internal.interaction.filter((object) => !
|
|
1471
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
925
1472
|
);
|
|
926
1473
|
}
|
|
927
1474
|
}
|
|
@@ -930,7 +1477,15 @@ function createEvents(store) {
|
|
|
930
1477
|
handleIntersects(hits, event, delta, onIntersect);
|
|
931
1478
|
};
|
|
932
1479
|
}
|
|
933
|
-
|
|
1480
|
+
function flushDeferredPointers() {
|
|
1481
|
+
const { internal, events } = store.getState();
|
|
1482
|
+
if (!events.frameTimedRaycasts) return;
|
|
1483
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1484
|
+
processDeferredPointer(event, pointerId);
|
|
1485
|
+
}
|
|
1486
|
+
internal.pointerDirty.clear();
|
|
1487
|
+
}
|
|
1488
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
934
1489
|
}
|
|
935
1490
|
const DOM_EVENTS = {
|
|
936
1491
|
onClick: ["click", false],
|
|
@@ -949,11 +1504,16 @@ const DOM_EVENTS = {
|
|
|
949
1504
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
950
1505
|
};
|
|
951
1506
|
function createPointerEvents(store) {
|
|
952
|
-
const { handlePointer } = createEvents(store);
|
|
1507
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1508
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1509
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
953
1510
|
return {
|
|
954
1511
|
priority: 1,
|
|
955
1512
|
enabled: true,
|
|
956
|
-
|
|
1513
|
+
frameTimedRaycasts: true,
|
|
1514
|
+
alwaysFireOnScroll: true,
|
|
1515
|
+
updateOnFrame: false,
|
|
1516
|
+
compute(event, state) {
|
|
957
1517
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
958
1518
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
959
1519
|
},
|
|
@@ -962,11 +1522,33 @@ function createPointerEvents(store) {
|
|
|
962
1522
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
963
1523
|
{}
|
|
964
1524
|
),
|
|
965
|
-
update: () => {
|
|
1525
|
+
update: (pointerId) => {
|
|
966
1526
|
const { events, internal } = store.getState();
|
|
967
|
-
if (
|
|
1527
|
+
if (!events.handlers) return;
|
|
1528
|
+
if (pointerId !== void 0) {
|
|
1529
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1530
|
+
if (event) {
|
|
1531
|
+
internal.pointerDirty.delete(pointerId);
|
|
1532
|
+
processDeferredPointer(event, pointerId);
|
|
1533
|
+
} else if (internal.lastEvent?.current) {
|
|
1534
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1535
|
+
}
|
|
1536
|
+
} else {
|
|
1537
|
+
flushDeferredPointers();
|
|
1538
|
+
if (internal.lastEvent?.current) {
|
|
1539
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
flush: () => {
|
|
1544
|
+
const { events, internal } = store.getState();
|
|
1545
|
+
flushDeferredPointers();
|
|
1546
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1547
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1548
|
+
}
|
|
968
1549
|
},
|
|
969
1550
|
connect: (target) => {
|
|
1551
|
+
if (!target) return;
|
|
970
1552
|
const { set, events } = store.getState();
|
|
971
1553
|
events.disconnect?.();
|
|
972
1554
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -990,6 +1572,32 @@ function createPointerEvents(store) {
|
|
|
990
1572
|
}
|
|
991
1573
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
992
1574
|
}
|
|
1575
|
+
},
|
|
1576
|
+
registerPointer: (config) => {
|
|
1577
|
+
const pointerId = nextXRPointerId++;
|
|
1578
|
+
xrPointers.set(pointerId, config);
|
|
1579
|
+
const { internal } = store.getState();
|
|
1580
|
+
getPointerState(internal, pointerId);
|
|
1581
|
+
return pointerId;
|
|
1582
|
+
},
|
|
1583
|
+
unregisterPointer: (pointerId) => {
|
|
1584
|
+
xrPointers.delete(pointerId);
|
|
1585
|
+
const { internal } = store.getState();
|
|
1586
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1587
|
+
if (pointerState) {
|
|
1588
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1589
|
+
const eventObject = hoveredObj.eventObject;
|
|
1590
|
+
const instance = eventObject.__r3f;
|
|
1591
|
+
if (instance?.eventCount) {
|
|
1592
|
+
const handlers = instance.handlers;
|
|
1593
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1594
|
+
handlers.onPointerOut?.(data);
|
|
1595
|
+
handlers.onPointerLeave?.(data);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
internal.pointerMap.delete(pointerId);
|
|
1599
|
+
}
|
|
1600
|
+
internal.pointerDirty.delete(pointerId);
|
|
993
1601
|
}
|
|
994
1602
|
};
|
|
995
1603
|
}
|
|
@@ -1080,23 +1688,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1080
1688
|
set,
|
|
1081
1689
|
get,
|
|
1082
1690
|
// Mock objects that have to be configured
|
|
1691
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1692
|
+
primaryStore: null,
|
|
1083
1693
|
gl: null,
|
|
1084
1694
|
renderer: null,
|
|
1085
1695
|
camera: null,
|
|
1086
1696
|
frustum: new webgpu.Frustum(),
|
|
1087
1697
|
autoUpdateFrustum: true,
|
|
1088
1698
|
raycaster: null,
|
|
1089
|
-
events: {
|
|
1699
|
+
events: {
|
|
1700
|
+
priority: 1,
|
|
1701
|
+
enabled: true,
|
|
1702
|
+
connected: false,
|
|
1703
|
+
frameTimedRaycasts: true,
|
|
1704
|
+
alwaysFireOnScroll: true,
|
|
1705
|
+
updateOnFrame: false
|
|
1706
|
+
},
|
|
1090
1707
|
scene: null,
|
|
1091
1708
|
rootScene: null,
|
|
1092
1709
|
xr: null,
|
|
1093
1710
|
inspector: null,
|
|
1094
1711
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1095
1712
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1096
|
-
|
|
1097
|
-
linear: false,
|
|
1098
|
-
flat: false,
|
|
1099
|
-
textureColorSpace: "srgb",
|
|
1713
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1100
1714
|
isLegacy: false,
|
|
1101
1715
|
webGPUSupported: false,
|
|
1102
1716
|
isNative: false,
|
|
@@ -1154,6 +1768,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1154
1768
|
size: newSize,
|
|
1155
1769
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1156
1770
|
}));
|
|
1771
|
+
scheduler.getScheduler().invalidate();
|
|
1157
1772
|
}
|
|
1158
1773
|
}
|
|
1159
1774
|
return;
|
|
@@ -1168,6 +1783,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1168
1783
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1169
1784
|
_sizeImperative: true
|
|
1170
1785
|
}));
|
|
1786
|
+
scheduler.getScheduler().invalidate();
|
|
1171
1787
|
},
|
|
1172
1788
|
setDpr: (dpr) => set((state2) => {
|
|
1173
1789
|
const resolved = calculateDpr(dpr);
|
|
@@ -1178,11 +1794,14 @@ const createStore = (invalidate, advance) => {
|
|
|
1178
1794
|
},
|
|
1179
1795
|
setError: (error) => set(() => ({ error })),
|
|
1180
1796
|
error: null,
|
|
1181
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1797
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1182
1798
|
uniforms: {},
|
|
1183
1799
|
nodes: {},
|
|
1800
|
+
buffers: {},
|
|
1801
|
+
gpuStorage: {},
|
|
1184
1802
|
textures: /* @__PURE__ */ new Map(),
|
|
1185
|
-
|
|
1803
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1804
|
+
renderPipeline: null,
|
|
1186
1805
|
passes: {},
|
|
1187
1806
|
_hmrVersion: 0,
|
|
1188
1807
|
_sizeImperative: false,
|
|
@@ -1191,12 +1810,16 @@ const createStore = (invalidate, advance) => {
|
|
|
1191
1810
|
internal: {
|
|
1192
1811
|
// Events
|
|
1193
1812
|
interaction: [],
|
|
1194
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1195
1813
|
subscribers: [],
|
|
1814
|
+
// Per-pointer state (new unified structure)
|
|
1815
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1816
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1817
|
+
lastEvent: React__namespace.createRef(),
|
|
1818
|
+
// Deprecated but kept for backwards compatibility
|
|
1819
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1196
1820
|
initialClick: [0, 0],
|
|
1197
1821
|
initialHits: [],
|
|
1198
1822
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1199
|
-
lastEvent: React__namespace.createRef(),
|
|
1200
1823
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1201
1824
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1202
1825
|
// Occlusion system (WebGPU only)
|
|
@@ -1279,13 +1902,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1279
1902
|
rootStore.subscribe(() => {
|
|
1280
1903
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1281
1904
|
const actualRenderer = internal.actualRenderer;
|
|
1905
|
+
const canvasTarget = internal.canvasTarget;
|
|
1282
1906
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1283
1907
|
oldSize = size;
|
|
1284
1908
|
oldDpr = viewport.dpr;
|
|
1285
1909
|
updateCamera(camera, size);
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1910
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1911
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1912
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1913
|
+
} else {
|
|
1914
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1915
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1916
|
+
if (canvasTarget) {
|
|
1917
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1918
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1289
1921
|
}
|
|
1290
1922
|
if (camera !== oldCamera) {
|
|
1291
1923
|
oldCamera = camera;
|
|
@@ -1356,1141 +1988,103 @@ useLoader.clear = function(loader, input) {
|
|
|
1356
1988
|
};
|
|
1357
1989
|
useLoader.loader = getLoader;
|
|
1358
1990
|
|
|
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
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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);
|
|
1458
|
-
}
|
|
1459
|
-
return "update";
|
|
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;
|
|
1991
|
+
function useFrame(callback, priorityOrOptions) {
|
|
1992
|
+
const store = React__namespace.useContext(context);
|
|
1993
|
+
const isInsideCanvas = store !== null;
|
|
1994
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
1995
|
+
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
1996
|
+
id: priorityOrOptions.id,
|
|
1997
|
+
phase: priorityOrOptions.phase,
|
|
1998
|
+
priority: priorityOrOptions.priority,
|
|
1999
|
+
fps: priorityOrOptions.fps,
|
|
2000
|
+
drop: priorityOrOptions.drop,
|
|
2001
|
+
enabled: priorityOrOptions.enabled,
|
|
2002
|
+
before: priorityOrOptions.before,
|
|
2003
|
+
after: priorityOrOptions.after
|
|
2004
|
+
}) : "";
|
|
2005
|
+
const options = React__namespace.useMemo(() => {
|
|
2006
|
+
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2007
|
+
}, [optionsKey]);
|
|
2008
|
+
const reactId = React__namespace.useId();
|
|
2009
|
+
const id = options.id ?? reactId;
|
|
2010
|
+
const callbackRef = useMutableCallback(callback);
|
|
2011
|
+
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2012
|
+
useIsomorphicLayoutEffect(() => {
|
|
2013
|
+
if (!callback) return;
|
|
2014
|
+
if (isInsideCanvas) {
|
|
2015
|
+
const state = store.getState();
|
|
2016
|
+
const rootId = state.internal.rootId;
|
|
2017
|
+
if (isLegacyPriority) {
|
|
2018
|
+
state.internal.priority++;
|
|
2019
|
+
let parentRoot = state.previousRoot;
|
|
2020
|
+
while (parentRoot) {
|
|
2021
|
+
const parentState = parentRoot.getState();
|
|
2022
|
+
if (parentState?.internal) parentState.internal.priority++;
|
|
2023
|
+
parentRoot = parentState?.previousRoot;
|
|
2024
|
+
}
|
|
2025
|
+
notifyDepreciated({
|
|
2026
|
+
heading: "useFrame with numeric priority is deprecated",
|
|
2027
|
+
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 })',
|
|
2028
|
+
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
const wrappedCallback = (frameState, delta) => {
|
|
2032
|
+
const localState = store.getState();
|
|
2033
|
+
const mergedState = {
|
|
2034
|
+
...localState,
|
|
2035
|
+
time: frameState.time,
|
|
2036
|
+
delta: frameState.delta,
|
|
2037
|
+
elapsed: frameState.elapsed,
|
|
2038
|
+
frame: frameState.frame
|
|
2039
|
+
};
|
|
2040
|
+
callbackRef.current?.(mergedState, delta);
|
|
2041
|
+
};
|
|
2042
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2043
|
+
id,
|
|
2044
|
+
rootId,
|
|
2045
|
+
...options
|
|
1517
2046
|
});
|
|
1518
|
-
|
|
2047
|
+
return () => {
|
|
2048
|
+
unregister();
|
|
2049
|
+
if (isLegacyPriority) {
|
|
2050
|
+
const currentState = store.getState();
|
|
2051
|
+
if (currentState.internal) {
|
|
2052
|
+
currentState.internal.priority--;
|
|
2053
|
+
let parentRoot = currentState.previousRoot;
|
|
2054
|
+
while (parentRoot) {
|
|
2055
|
+
const parentState = parentRoot.getState();
|
|
2056
|
+
if (parentState?.internal) parentState.internal.priority--;
|
|
2057
|
+
parentRoot = parentState?.previousRoot;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
} else {
|
|
2063
|
+
return scheduler$1.register(
|
|
2064
|
+
(state, delta) => {
|
|
2065
|
+
const frameState = state;
|
|
2066
|
+
if (!frameState.renderer) return;
|
|
2067
|
+
callbackRef.current?.(frameState, delta);
|
|
2068
|
+
},
|
|
2069
|
+
{ id, ...options }
|
|
2070
|
+
);
|
|
1519
2071
|
}
|
|
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]);
|
|
2072
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2479
2073
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2480
2074
|
// Subscribe function
|
|
2481
2075
|
React__namespace.useCallback(
|
|
2482
2076
|
(onStoreChange) => {
|
|
2483
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2077
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2484
2078
|
},
|
|
2485
2079
|
[id]
|
|
2486
2080
|
),
|
|
2487
2081
|
// getSnapshot function
|
|
2488
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2082
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2489
2083
|
// getServerSnapshot function (SSR)
|
|
2490
2084
|
React__namespace.useCallback(() => false, [])
|
|
2491
2085
|
);
|
|
2492
2086
|
const controls = React__namespace.useMemo(() => {
|
|
2493
|
-
const scheduler2 = getScheduler();
|
|
2087
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2494
2088
|
return {
|
|
2495
2089
|
/** The job's unique ID */
|
|
2496
2090
|
id,
|
|
@@ -2505,7 +2099,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2505
2099
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2506
2100
|
*/
|
|
2507
2101
|
step: (timestamp) => {
|
|
2508
|
-
getScheduler().stepJob(id, timestamp);
|
|
2102
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2509
2103
|
},
|
|
2510
2104
|
/**
|
|
2511
2105
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2513,20 +2107,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2513
2107
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2514
2108
|
*/
|
|
2515
2109
|
stepAll: (timestamp) => {
|
|
2516
|
-
getScheduler().step(timestamp);
|
|
2110
|
+
scheduler.getScheduler().step(timestamp);
|
|
2517
2111
|
},
|
|
2518
2112
|
/**
|
|
2519
2113
|
* Pause this job (set enabled=false).
|
|
2520
2114
|
* Job remains registered but won't run.
|
|
2521
2115
|
*/
|
|
2522
2116
|
pause: () => {
|
|
2523
|
-
getScheduler().pauseJob(id);
|
|
2117
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2524
2118
|
},
|
|
2525
2119
|
/**
|
|
2526
2120
|
* Resume this job (set enabled=true).
|
|
2527
2121
|
*/
|
|
2528
2122
|
resume: () => {
|
|
2529
|
-
getScheduler().resumeJob(id);
|
|
2123
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2530
2124
|
},
|
|
2531
2125
|
/**
|
|
2532
2126
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2564,22 +2158,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2564
2158
|
function useTexture(input, optionsOrOnLoad) {
|
|
2565
2159
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2566
2160
|
const store = useStore();
|
|
2567
|
-
const textureCache = useThree((state) => state.textures);
|
|
2568
2161
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2569
|
-
const { onLoad, cache =
|
|
2162
|
+
const { onLoad, cache = true } = options;
|
|
2163
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2164
|
+
onLoadRef.current = onLoad;
|
|
2165
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2570
2166
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2571
2167
|
const cachedResult = React.useMemo(() => {
|
|
2572
2168
|
if (!cache) return null;
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2169
|
+
const textures = store.getState().textures;
|
|
2170
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2171
|
+
return buildFromCache(input, textures);
|
|
2172
|
+
}, [cache, urls, input, store]);
|
|
2576
2173
|
const loadedTextures = useLoader(
|
|
2577
2174
|
webgpu.TextureLoader,
|
|
2578
2175
|
IsObject(input) ? Object.values(input) : input
|
|
2579
2176
|
);
|
|
2177
|
+
const inputKey = urls.join("\0");
|
|
2580
2178
|
React.useLayoutEffect(() => {
|
|
2581
|
-
if (
|
|
2582
|
-
|
|
2179
|
+
if (cachedResult) return;
|
|
2180
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2181
|
+
onLoadCalledForRef.current = inputKey;
|
|
2182
|
+
onLoadRef.current?.(loadedTextures);
|
|
2183
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2583
2184
|
React.useEffect(() => {
|
|
2584
2185
|
if (cachedResult) return;
|
|
2585
2186
|
if ("initTexture" in renderer) {
|
|
@@ -2612,8 +2213,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2612
2213
|
}, [input, loadedTextures, cachedResult]);
|
|
2613
2214
|
React.useEffect(() => {
|
|
2614
2215
|
if (!cache) return;
|
|
2615
|
-
if (cachedResult) return;
|
|
2616
|
-
const set = store.setState;
|
|
2617
2216
|
const urlTextureMap = [];
|
|
2618
2217
|
if (typeof input === "string") {
|
|
2619
2218
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2627,18 +2226,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2627
2226
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2628
2227
|
}
|
|
2629
2228
|
}
|
|
2630
|
-
|
|
2631
|
-
const
|
|
2632
|
-
let
|
|
2229
|
+
store.setState((state) => {
|
|
2230
|
+
const refs = new Map(state._textureRefs);
|
|
2231
|
+
let textures = state.textures;
|
|
2232
|
+
let added = false;
|
|
2633
2233
|
for (const [url, texture] of urlTextureMap) {
|
|
2634
|
-
if (!
|
|
2635
|
-
|
|
2636
|
-
|
|
2234
|
+
if (!textures.has(url)) {
|
|
2235
|
+
if (!added) {
|
|
2236
|
+
textures = new Map(textures);
|
|
2237
|
+
added = true;
|
|
2238
|
+
}
|
|
2239
|
+
textures.set(url, texture);
|
|
2637
2240
|
}
|
|
2241
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2638
2242
|
}
|
|
2639
|
-
return
|
|
2243
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2244
|
+
});
|
|
2245
|
+
return () => store.setState((state) => {
|
|
2246
|
+
const refs = new Map(state._textureRefs);
|
|
2247
|
+
for (const [url] of urlTextureMap) {
|
|
2248
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2249
|
+
if (next <= 0) refs.delete(url);
|
|
2250
|
+
else refs.set(url, next);
|
|
2251
|
+
}
|
|
2252
|
+
return { _textureRefs: refs };
|
|
2640
2253
|
});
|
|
2641
|
-
}, [cache, input, mappedTextures, store
|
|
2254
|
+
}, [cache, input, mappedTextures, store]);
|
|
2642
2255
|
return mappedTextures;
|
|
2643
2256
|
}
|
|
2644
2257
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2654,106 +2267,90 @@ const Texture = ({
|
|
|
2654
2267
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2655
2268
|
};
|
|
2656
2269
|
|
|
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() {
|
|
2270
|
+
function useTextures(selector) {
|
|
2665
2271
|
const store = useStore();
|
|
2666
|
-
|
|
2667
|
-
const set = store.setState;
|
|
2272
|
+
const registry = React.useMemo(() => {
|
|
2668
2273
|
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
|
-
};
|
|
2274
|
+
const setState = store.setState;
|
|
2275
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2727
2276
|
return {
|
|
2728
|
-
|
|
2729
|
-
get textures() {
|
|
2277
|
+
get all() {
|
|
2730
2278
|
return getState().textures;
|
|
2731
2279
|
},
|
|
2732
|
-
|
|
2733
|
-
|
|
2280
|
+
get(input) {
|
|
2281
|
+
if (typeof input === "string") return getOne(input);
|
|
2282
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2283
|
+
const out = {};
|
|
2284
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2285
|
+
return out;
|
|
2286
|
+
},
|
|
2734
2287
|
has: (key) => getState().textures.has(key),
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2288
|
+
add(keyOrRecord, texture) {
|
|
2289
|
+
setState((state) => {
|
|
2290
|
+
const textures = new Map(state.textures);
|
|
2291
|
+
if (typeof keyOrRecord === "string") {
|
|
2292
|
+
textures.set(keyOrRecord, texture);
|
|
2293
|
+
} else {
|
|
2294
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2295
|
+
}
|
|
2296
|
+
return { textures };
|
|
2297
|
+
});
|
|
2298
|
+
},
|
|
2299
|
+
dispose(key, options) {
|
|
2300
|
+
const state = getState();
|
|
2301
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2302
|
+
if (refs > 0 && !options?.force) {
|
|
2303
|
+
console.warn(
|
|
2304
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2305
|
+
);
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
state.textures.get(key)?.dispose();
|
|
2309
|
+
setState((s) => {
|
|
2310
|
+
const textures = new Map(s.textures);
|
|
2311
|
+
textures.delete(key);
|
|
2312
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2313
|
+
nextRefs.delete(key);
|
|
2314
|
+
return { textures, _textureRefs: nextRefs };
|
|
2315
|
+
});
|
|
2316
|
+
return true;
|
|
2317
|
+
},
|
|
2318
|
+
disposeAll() {
|
|
2319
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2320
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2321
|
+
}
|
|
2745
2322
|
};
|
|
2746
2323
|
}, [store]);
|
|
2324
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2325
|
+
const selected = useThree(subscribe);
|
|
2326
|
+
return selector ? selected : registry;
|
|
2747
2327
|
}
|
|
2748
2328
|
|
|
2749
|
-
function useRenderTarget(
|
|
2329
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2750
2330
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2751
2331
|
const size = useThree((s) => s.size);
|
|
2332
|
+
let width;
|
|
2333
|
+
let height;
|
|
2334
|
+
let opts;
|
|
2335
|
+
if (typeof widthOrOptions === "object") {
|
|
2336
|
+
opts = widthOrOptions;
|
|
2337
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2338
|
+
width = widthOrOptions;
|
|
2339
|
+
if (typeof heightOrOptions === "object") {
|
|
2340
|
+
height = widthOrOptions;
|
|
2341
|
+
opts = heightOrOptions;
|
|
2342
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2343
|
+
height = heightOrOptions;
|
|
2344
|
+
opts = options;
|
|
2345
|
+
} else {
|
|
2346
|
+
height = widthOrOptions;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2752
2349
|
return React.useMemo(() => {
|
|
2753
2350
|
const w = width ?? size.width;
|
|
2754
2351
|
const h = height ?? size.height;
|
|
2755
|
-
return new webgpu.RenderTarget(w, h,
|
|
2756
|
-
}, [width, height, size.width, size.height,
|
|
2352
|
+
return new webgpu.RenderTarget(w, h, opts);
|
|
2353
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2757
2354
|
}
|
|
2758
2355
|
|
|
2759
2356
|
function useStore() {
|
|
@@ -2781,7 +2378,7 @@ function addEffect(callback) {
|
|
|
2781
2378
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2782
2379
|
});
|
|
2783
2380
|
const id = `legacy_effect_${effectId++}`;
|
|
2784
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2381
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2785
2382
|
}
|
|
2786
2383
|
function addAfterEffect(callback) {
|
|
2787
2384
|
notifyDepreciated({
|
|
@@ -2790,7 +2387,7 @@ function addAfterEffect(callback) {
|
|
|
2790
2387
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2791
2388
|
});
|
|
2792
2389
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2793
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2390
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2794
2391
|
}
|
|
2795
2392
|
function addTail(callback) {
|
|
2796
2393
|
notifyDepreciated({
|
|
@@ -2798,13 +2395,13 @@ function addTail(callback) {
|
|
|
2798
2395
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2799
2396
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2800
2397
|
});
|
|
2801
|
-
return getScheduler().onIdle(callback);
|
|
2398
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2802
2399
|
}
|
|
2803
2400
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2804
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2401
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2805
2402
|
}
|
|
2806
|
-
function advance(timestamp
|
|
2807
|
-
getScheduler().step(timestamp);
|
|
2403
|
+
function advance(timestamp) {
|
|
2404
|
+
scheduler.getScheduler().step(timestamp);
|
|
2808
2405
|
}
|
|
2809
2406
|
|
|
2810
2407
|
const version = "10.0.0-alpha.2";
|
|
@@ -14257,6 +13854,7 @@ function swapInstances() {
|
|
|
14257
13854
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14258
13855
|
instance.object.__r3f = instance;
|
|
14259
13856
|
setFiberRef(fiber, instance.object);
|
|
13857
|
+
delete instance.appliedOnce;
|
|
14260
13858
|
applyProps(instance.object, instance.props);
|
|
14261
13859
|
if (instance.props.attach) {
|
|
14262
13860
|
attach(parent, instance);
|
|
@@ -14330,8 +13928,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14330
13928
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14331
13929
|
if (isTailSibling) swapInstances();
|
|
14332
13930
|
},
|
|
14333
|
-
finalizeInitialChildren: () =>
|
|
14334
|
-
|
|
13931
|
+
finalizeInitialChildren: (instance) => {
|
|
13932
|
+
for (const prop in instance.props) {
|
|
13933
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13934
|
+
}
|
|
13935
|
+
return false;
|
|
13936
|
+
},
|
|
13937
|
+
commitMount(instance) {
|
|
13938
|
+
const resolved = {};
|
|
13939
|
+
for (const prop in instance.props) {
|
|
13940
|
+
const value = instance.props[prop];
|
|
13941
|
+
if (isFromRef(value)) {
|
|
13942
|
+
const ref = value[FROM_REF];
|
|
13943
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14335
13947
|
},
|
|
14336
13948
|
getPublicInstance: (instance) => instance?.object,
|
|
14337
13949
|
prepareForCommit: () => null,
|
|
@@ -14552,6 +14164,9 @@ function createRoot(canvas) {
|
|
|
14552
14164
|
let resolve;
|
|
14553
14165
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14554
14166
|
const {
|
|
14167
|
+
id: canvasId,
|
|
14168
|
+
primaryCanvas,
|
|
14169
|
+
scheduler: schedulerConfig,
|
|
14555
14170
|
gl: glConfig,
|
|
14556
14171
|
renderer: rendererConfig,
|
|
14557
14172
|
size: propsSize,
|
|
@@ -14559,10 +14174,6 @@ function createRoot(canvas) {
|
|
|
14559
14174
|
events,
|
|
14560
14175
|
onCreated: onCreatedCallback,
|
|
14561
14176
|
shadows = false,
|
|
14562
|
-
linear = false,
|
|
14563
|
-
flat = false,
|
|
14564
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14565
|
-
legacy = false,
|
|
14566
14177
|
orthographic = false,
|
|
14567
14178
|
frameloop = "always",
|
|
14568
14179
|
dpr = [1, 2],
|
|
@@ -14574,11 +14185,14 @@ function createRoot(canvas) {
|
|
|
14574
14185
|
onDropMissed,
|
|
14575
14186
|
autoUpdateFrustum = true,
|
|
14576
14187
|
occlusion = false,
|
|
14577
|
-
_sizeProps
|
|
14188
|
+
_sizeProps,
|
|
14189
|
+
forceEven
|
|
14578
14190
|
} = props;
|
|
14191
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14579
14192
|
const state = store.getState();
|
|
14580
14193
|
const defaultGPUProps = {
|
|
14581
|
-
canvas
|
|
14194
|
+
canvas,
|
|
14195
|
+
antialias: true
|
|
14582
14196
|
};
|
|
14583
14197
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14584
14198
|
throw new Error(
|
|
@@ -14589,15 +14203,52 @@ function createRoot(canvas) {
|
|
|
14589
14203
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
14590
14204
|
}
|
|
14591
14205
|
let renderer = state.internal.actualRenderer;
|
|
14592
|
-
if (!state.internal.actualRenderer) {
|
|
14206
|
+
if (primaryCanvas && !state.internal.actualRenderer) {
|
|
14207
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14208
|
+
renderer = primary.renderer;
|
|
14209
|
+
state.internal.actualRenderer = renderer;
|
|
14210
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14211
|
+
primary.store.setState((prev) => ({
|
|
14212
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14213
|
+
}));
|
|
14214
|
+
state.set((prev) => ({
|
|
14215
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14216
|
+
renderer,
|
|
14217
|
+
primaryStore: primary.store,
|
|
14218
|
+
internal: {
|
|
14219
|
+
...prev.internal,
|
|
14220
|
+
canvasTarget,
|
|
14221
|
+
isMultiCanvas: true,
|
|
14222
|
+
isSecondary: true,
|
|
14223
|
+
targetId: primaryCanvas
|
|
14224
|
+
}
|
|
14225
|
+
}));
|
|
14226
|
+
} else if (!state.internal.actualRenderer) {
|
|
14593
14227
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14594
14228
|
if (!renderer.hasInitialized?.()) {
|
|
14229
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14230
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14231
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14232
|
+
canvas.width = size2.width * pixelRatio;
|
|
14233
|
+
canvas.height = size2.height * pixelRatio;
|
|
14234
|
+
}
|
|
14595
14235
|
await renderer.init();
|
|
14596
14236
|
}
|
|
14597
14237
|
const backend = renderer.backend;
|
|
14598
14238
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14599
14239
|
state.internal.actualRenderer = renderer;
|
|
14600
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14240
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14241
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14242
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14243
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14244
|
+
state.set((prev) => ({
|
|
14245
|
+
internal: {
|
|
14246
|
+
...prev.internal,
|
|
14247
|
+
canvasTarget,
|
|
14248
|
+
unregisterPrimary
|
|
14249
|
+
}
|
|
14250
|
+
}));
|
|
14251
|
+
}
|
|
14601
14252
|
}
|
|
14602
14253
|
let raycaster = state.raycaster;
|
|
14603
14254
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -14606,6 +14257,7 @@ function createRoot(canvas) {
|
|
|
14606
14257
|
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14607
14258
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14608
14259
|
}
|
|
14260
|
+
let tempCamera = state.camera;
|
|
14609
14261
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14610
14262
|
lastCamera = cameraOptions;
|
|
14611
14263
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14625,6 +14277,7 @@ function createRoot(canvas) {
|
|
|
14625
14277
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14626
14278
|
}
|
|
14627
14279
|
state.set({ camera });
|
|
14280
|
+
tempCamera = camera;
|
|
14628
14281
|
raycaster.camera = camera;
|
|
14629
14282
|
}
|
|
14630
14283
|
if (!state.scene) {
|
|
@@ -14642,7 +14295,7 @@ function createRoot(canvas) {
|
|
|
14642
14295
|
rootScene: scene,
|
|
14643
14296
|
internal: { ...prev.internal, container: scene }
|
|
14644
14297
|
}));
|
|
14645
|
-
const camera =
|
|
14298
|
+
const camera = tempCamera;
|
|
14646
14299
|
if (camera && !camera.parent) scene.add(camera);
|
|
14647
14300
|
}
|
|
14648
14301
|
if (events && !state.events.handlers) {
|
|
@@ -14659,6 +14312,9 @@ function createRoot(canvas) {
|
|
|
14659
14312
|
if (_sizeProps !== void 0) {
|
|
14660
14313
|
state.set({ _sizeProps });
|
|
14661
14314
|
}
|
|
14315
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14316
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14317
|
+
}
|
|
14662
14318
|
const size = computeInitialSize(canvas, propsSize);
|
|
14663
14319
|
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14664
14320
|
const wasImperative = state._sizeImperative;
|
|
@@ -14685,10 +14341,10 @@ function createRoot(canvas) {
|
|
|
14685
14341
|
lastConfiguredProps.performance = performance;
|
|
14686
14342
|
}
|
|
14687
14343
|
if (!state.xr) {
|
|
14688
|
-
const handleXRFrame = (timestamp,
|
|
14344
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14689
14345
|
const state2 = store.getState();
|
|
14690
14346
|
if (state2.frameloop === "never") return;
|
|
14691
|
-
advance(timestamp
|
|
14347
|
+
advance(timestamp);
|
|
14692
14348
|
};
|
|
14693
14349
|
const actualRenderer = state.internal.actualRenderer;
|
|
14694
14350
|
const handleSessionChange = () => {
|
|
@@ -14700,16 +14356,16 @@ function createRoot(canvas) {
|
|
|
14700
14356
|
};
|
|
14701
14357
|
const xr = {
|
|
14702
14358
|
connect() {
|
|
14703
|
-
const { gl, renderer: renderer2
|
|
14704
|
-
const
|
|
14705
|
-
|
|
14706
|
-
|
|
14359
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14360
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14361
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14362
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14707
14363
|
},
|
|
14708
14364
|
disconnect() {
|
|
14709
|
-
const { gl, renderer: renderer2
|
|
14710
|
-
const
|
|
14711
|
-
|
|
14712
|
-
|
|
14365
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14366
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14367
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14368
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14713
14369
|
}
|
|
14714
14370
|
};
|
|
14715
14371
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14721,15 +14377,22 @@ function createRoot(canvas) {
|
|
|
14721
14377
|
const oldType = renderer.shadowMap.type;
|
|
14722
14378
|
renderer.shadowMap.enabled = !!shadows;
|
|
14723
14379
|
if (is.boo(shadows)) {
|
|
14724
|
-
renderer.shadowMap.type = webgpu.
|
|
14380
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14725
14381
|
} else if (is.str(shadows)) {
|
|
14382
|
+
if (shadows === "soft") {
|
|
14383
|
+
notifyDepreciated({
|
|
14384
|
+
heading: 'shadows="soft" is deprecated',
|
|
14385
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14386
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14387
|
+
});
|
|
14388
|
+
}
|
|
14726
14389
|
const types = {
|
|
14727
14390
|
basic: webgpu.BasicShadowMap,
|
|
14728
14391
|
percentage: webgpu.PCFShadowMap,
|
|
14729
|
-
soft: webgpu.
|
|
14392
|
+
soft: webgpu.PCFShadowMap,
|
|
14730
14393
|
variance: webgpu.VSMShadowMap
|
|
14731
14394
|
};
|
|
14732
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14395
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14733
14396
|
} else if (is.obj(shadows)) {
|
|
14734
14397
|
Object.assign(renderer.shadowMap, shadows);
|
|
14735
14398
|
}
|
|
@@ -14737,28 +14400,70 @@ function createRoot(canvas) {
|
|
|
14737
14400
|
renderer.shadowMap.needsUpdate = true;
|
|
14738
14401
|
}
|
|
14739
14402
|
}
|
|
14403
|
+
if (!configured) {
|
|
14404
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14405
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14406
|
+
}
|
|
14740
14407
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14741
14408
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14742
14409
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14743
14410
|
}
|
|
14411
|
+
const r3fProps = ["textureColorSpace"];
|
|
14412
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14413
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14744
14414
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14745
|
-
|
|
14415
|
+
const glProps = {};
|
|
14416
|
+
for (const key in glConfig) {
|
|
14417
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14418
|
+
}
|
|
14419
|
+
applyProps(renderer, glProps);
|
|
14746
14420
|
}
|
|
14747
14421
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14748
14422
|
const currentRenderer = state.renderer;
|
|
14749
14423
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14750
|
-
|
|
14424
|
+
const rendererProps = {};
|
|
14425
|
+
for (const key in rendererConfig) {
|
|
14426
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14427
|
+
}
|
|
14428
|
+
applyProps(currentRenderer, rendererProps);
|
|
14751
14429
|
}
|
|
14752
14430
|
}
|
|
14753
|
-
const scheduler = getScheduler();
|
|
14431
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14754
14432
|
const rootId = state.internal.rootId;
|
|
14755
14433
|
if (!rootId) {
|
|
14756
|
-
const newRootId = scheduler.generateRootId();
|
|
14757
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14434
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14435
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14758
14436
|
getState: () => store.getState(),
|
|
14759
14437
|
onError: (err) => store.getState().setError(err)
|
|
14760
14438
|
});
|
|
14761
|
-
const
|
|
14439
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14440
|
+
() => {
|
|
14441
|
+
const state2 = store.getState();
|
|
14442
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14443
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14444
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14445
|
+
}
|
|
14446
|
+
},
|
|
14447
|
+
{
|
|
14448
|
+
id: `${newRootId}_canvasTarget`,
|
|
14449
|
+
rootId: newRootId,
|
|
14450
|
+
phase: "start",
|
|
14451
|
+
system: true
|
|
14452
|
+
}
|
|
14453
|
+
);
|
|
14454
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14455
|
+
() => {
|
|
14456
|
+
const state2 = store.getState();
|
|
14457
|
+
state2.events.flush?.();
|
|
14458
|
+
},
|
|
14459
|
+
{
|
|
14460
|
+
id: `${newRootId}_events`,
|
|
14461
|
+
rootId: newRootId,
|
|
14462
|
+
phase: "input",
|
|
14463
|
+
system: true
|
|
14464
|
+
}
|
|
14465
|
+
);
|
|
14466
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14762
14467
|
() => {
|
|
14763
14468
|
const state2 = store.getState();
|
|
14764
14469
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14768,11 +14473,11 @@ function createRoot(canvas) {
|
|
|
14768
14473
|
{
|
|
14769
14474
|
id: `${newRootId}_frustum`,
|
|
14770
14475
|
rootId: newRootId,
|
|
14771
|
-
|
|
14476
|
+
before: "render",
|
|
14772
14477
|
system: true
|
|
14773
14478
|
}
|
|
14774
14479
|
);
|
|
14775
|
-
const unregisterVisibility = scheduler.register(
|
|
14480
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14776
14481
|
() => {
|
|
14777
14482
|
const state2 = store.getState();
|
|
14778
14483
|
checkVisibility(state2);
|
|
@@ -14780,30 +14485,34 @@ function createRoot(canvas) {
|
|
|
14780
14485
|
{
|
|
14781
14486
|
id: `${newRootId}_visibility`,
|
|
14782
14487
|
rootId: newRootId,
|
|
14783
|
-
|
|
14488
|
+
before: "render",
|
|
14784
14489
|
system: true,
|
|
14785
14490
|
after: `${newRootId}_frustum`
|
|
14786
14491
|
}
|
|
14787
14492
|
);
|
|
14788
|
-
const unregisterRender = scheduler.register(
|
|
14493
|
+
const unregisterRender = scheduler$1.register(
|
|
14789
14494
|
() => {
|
|
14790
14495
|
const state2 = store.getState();
|
|
14791
14496
|
const renderer2 = state2.internal.actualRenderer;
|
|
14792
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14497
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14793
14498
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14794
14499
|
try {
|
|
14795
|
-
if (state2.
|
|
14500
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14796
14501
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14797
14502
|
} catch (error) {
|
|
14798
14503
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14799
14504
|
}
|
|
14800
14505
|
},
|
|
14801
14506
|
{
|
|
14802
|
-
|
|
14507
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14508
|
+
id: canvasId || `${newRootId}_render`,
|
|
14803
14509
|
rootId: newRootId,
|
|
14804
14510
|
phase: "render",
|
|
14805
|
-
system: true
|
|
14511
|
+
system: true,
|
|
14806
14512
|
// Internal flag: this is a system job, not user-controlled
|
|
14513
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14514
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14515
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14807
14516
|
}
|
|
14808
14517
|
);
|
|
14809
14518
|
state.set((state2) => ({
|
|
@@ -14812,15 +14521,17 @@ function createRoot(canvas) {
|
|
|
14812
14521
|
rootId: newRootId,
|
|
14813
14522
|
unregisterRoot: () => {
|
|
14814
14523
|
unregisterRoot();
|
|
14524
|
+
unregisterCanvasTarget();
|
|
14525
|
+
unregisterEventsFlush();
|
|
14815
14526
|
unregisterFrustum();
|
|
14816
14527
|
unregisterVisibility();
|
|
14817
14528
|
unregisterRender();
|
|
14818
14529
|
},
|
|
14819
|
-
scheduler
|
|
14530
|
+
scheduler: scheduler$1
|
|
14820
14531
|
}
|
|
14821
14532
|
}));
|
|
14822
14533
|
}
|
|
14823
|
-
scheduler.frameloop = frameloop;
|
|
14534
|
+
scheduler$1.frameloop = frameloop;
|
|
14824
14535
|
onCreated = onCreatedCallback;
|
|
14825
14536
|
configured = true;
|
|
14826
14537
|
resolve();
|
|
@@ -14870,15 +14581,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14870
14581
|
const renderer = state.internal.actualRenderer;
|
|
14871
14582
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14872
14583
|
if (unregisterRoot) unregisterRoot();
|
|
14584
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14585
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14586
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14587
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14873
14588
|
state.events.disconnect?.();
|
|
14874
14589
|
cleanupHelperGroup(root.store);
|
|
14875
|
-
renderer
|
|
14876
|
-
|
|
14877
|
-
|
|
14590
|
+
if (state.isLegacy && renderer) {
|
|
14591
|
+
;
|
|
14592
|
+
renderer.renderLists?.dispose?.();
|
|
14593
|
+
renderer.forceContextLoss?.();
|
|
14594
|
+
}
|
|
14595
|
+
if (!state.internal.isSecondary) {
|
|
14596
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14597
|
+
}
|
|
14878
14598
|
dispose(state.scene);
|
|
14879
14599
|
_roots.delete(canvas);
|
|
14880
14600
|
if (callback) callback(canvas);
|
|
14881
|
-
} catch
|
|
14601
|
+
} catch {
|
|
14882
14602
|
}
|
|
14883
14603
|
}, 500);
|
|
14884
14604
|
}
|
|
@@ -14886,36 +14606,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14886
14606
|
}
|
|
14887
14607
|
}
|
|
14888
14608
|
function createPortal(children, container, state) {
|
|
14889
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14609
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14890
14610
|
}
|
|
14891
|
-
function
|
|
14611
|
+
function Portal({ children, container, state }) {
|
|
14892
14612
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14893
|
-
const [resolvedContainer,
|
|
14613
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14894
14614
|
if (isRef(container)) return container.current ?? null;
|
|
14895
14615
|
return container;
|
|
14896
14616
|
});
|
|
14617
|
+
const setResolvedContainer = React.useCallback(
|
|
14618
|
+
(newContainer) => {
|
|
14619
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14620
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14621
|
+
},
|
|
14622
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14623
|
+
);
|
|
14897
14624
|
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);
|
|
14625
|
+
if (isRef(container) && !container.current) {
|
|
14626
|
+
return queueMicrotask(() => {
|
|
14627
|
+
setResolvedContainer(container.current);
|
|
14628
|
+
});
|
|
14912
14629
|
}
|
|
14913
|
-
|
|
14630
|
+
setResolvedContainer(container);
|
|
14631
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14914
14632
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14915
14633
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14916
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14634
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14917
14635
|
}
|
|
14918
|
-
function
|
|
14636
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14919
14637
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14920
14638
|
const previousRoot = useStore();
|
|
14921
14639
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14936,11 +14654,12 @@ function Portal({ state = {}, children, container }) {
|
|
|
14936
14654
|
};
|
|
14937
14655
|
}, [portalScene, container, injectScene]);
|
|
14938
14656
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14657
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14939
14658
|
let viewport = void 0;
|
|
14940
|
-
if (injectState.camera && size) {
|
|
14659
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14941
14660
|
const camera = injectState.camera;
|
|
14942
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14943
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14661
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14662
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14944
14663
|
}
|
|
14945
14664
|
return {
|
|
14946
14665
|
// The intersect consists of the previous root state
|
|
@@ -14957,7 +14676,7 @@ function Portal({ state = {}, children, container }) {
|
|
|
14957
14676
|
previousRoot,
|
|
14958
14677
|
// Events, size and viewport can be overridden by the inject layer
|
|
14959
14678
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14960
|
-
size:
|
|
14679
|
+
size: resolvedSize,
|
|
14961
14680
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14962
14681
|
// Layers are allowed to override events
|
|
14963
14682
|
setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
@@ -14969,9 +14688,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14969
14688
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14970
14689
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14971
14690
|
onMutate(previousRoot.getState());
|
|
14972
|
-
previousRoot.subscribe(onMutate);
|
|
14973
14691
|
return store;
|
|
14974
14692
|
}, [previousRoot, container]);
|
|
14693
|
+
useIsomorphicLayoutEffect(() => {
|
|
14694
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14695
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14696
|
+
return unsubscribe;
|
|
14697
|
+
}, [previousRoot, usePortalStore]);
|
|
14975
14698
|
return (
|
|
14976
14699
|
// @ts-ignore, reconciler types are not maintained
|
|
14977
14700
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -14985,21 +14708,59 @@ function flushSync(fn) {
|
|
|
14985
14708
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14986
14709
|
}
|
|
14987
14710
|
|
|
14711
|
+
function parseBackground(background) {
|
|
14712
|
+
if (!background) return null;
|
|
14713
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14714
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14715
|
+
return {
|
|
14716
|
+
...rest,
|
|
14717
|
+
preset,
|
|
14718
|
+
files: envMap || files,
|
|
14719
|
+
backgroundFiles: backgroundMap,
|
|
14720
|
+
background: true
|
|
14721
|
+
};
|
|
14722
|
+
}
|
|
14723
|
+
if (typeof background === "number") {
|
|
14724
|
+
return { color: background, background: true };
|
|
14725
|
+
}
|
|
14726
|
+
if (typeof background === "string") {
|
|
14727
|
+
if (background in presetsObj) {
|
|
14728
|
+
return { preset: background, background: true };
|
|
14729
|
+
}
|
|
14730
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14731
|
+
return { files: background, background: true };
|
|
14732
|
+
}
|
|
14733
|
+
return { color: background, background: true };
|
|
14734
|
+
}
|
|
14735
|
+
if (background.isColor) {
|
|
14736
|
+
return { color: background, background: true };
|
|
14737
|
+
}
|
|
14738
|
+
return null;
|
|
14739
|
+
}
|
|
14740
|
+
|
|
14741
|
+
function clearHmrCaches(store) {
|
|
14742
|
+
store.setState((state) => ({
|
|
14743
|
+
nodes: {},
|
|
14744
|
+
uniforms: {},
|
|
14745
|
+
buffers: {},
|
|
14746
|
+
gpuStorage: {},
|
|
14747
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14748
|
+
}));
|
|
14749
|
+
}
|
|
14750
|
+
|
|
14988
14751
|
function CanvasImpl({
|
|
14989
14752
|
ref,
|
|
14990
14753
|
children,
|
|
14991
14754
|
fallback,
|
|
14992
14755
|
resize,
|
|
14993
14756
|
style,
|
|
14757
|
+
id,
|
|
14994
14758
|
gl,
|
|
14995
|
-
renderer,
|
|
14759
|
+
renderer: rendererProp,
|
|
14996
14760
|
events = createPointerEvents,
|
|
14997
14761
|
eventSource,
|
|
14998
14762
|
eventPrefix,
|
|
14999
14763
|
shadows,
|
|
15000
|
-
linear,
|
|
15001
|
-
flat,
|
|
15002
|
-
legacy,
|
|
15003
14764
|
orthographic,
|
|
15004
14765
|
frameloop,
|
|
15005
14766
|
dpr,
|
|
@@ -15007,6 +14768,8 @@ function CanvasImpl({
|
|
|
15007
14768
|
raycaster,
|
|
15008
14769
|
camera,
|
|
15009
14770
|
scene,
|
|
14771
|
+
autoUpdateFrustum,
|
|
14772
|
+
occlusion,
|
|
15010
14773
|
onPointerMissed,
|
|
15011
14774
|
onDragOverMissed,
|
|
15012
14775
|
onDropMissed,
|
|
@@ -15014,10 +14777,25 @@ function CanvasImpl({
|
|
|
15014
14777
|
hmr,
|
|
15015
14778
|
width,
|
|
15016
14779
|
height,
|
|
14780
|
+
background,
|
|
14781
|
+
forceEven,
|
|
15017
14782
|
...props
|
|
15018
14783
|
}) {
|
|
14784
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14785
|
+
let primaryCanvas;
|
|
14786
|
+
let scheduler;
|
|
14787
|
+
let renderer;
|
|
14788
|
+
if (isRendererConfig) {
|
|
14789
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14790
|
+
primaryCanvas = pc;
|
|
14791
|
+
scheduler = sc;
|
|
14792
|
+
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
14793
|
+
} else {
|
|
14794
|
+
renderer = rendererProp;
|
|
14795
|
+
}
|
|
15019
14796
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15020
14797
|
const Bridge = useBridge();
|
|
14798
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15021
14799
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15022
14800
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15023
14801
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15034,15 +14812,20 @@ function CanvasImpl({
|
|
|
15034
14812
|
};
|
|
15035
14813
|
}, [resize, hasInitialSizeRef.current]);
|
|
15036
14814
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15037
|
-
const effectiveSize = React__namespace.useMemo(
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
14815
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14816
|
+
let w = width ?? containerRect.width;
|
|
14817
|
+
let h = height ?? containerRect.height;
|
|
14818
|
+
if (forceEven) {
|
|
14819
|
+
w = Math.ceil(w / 2) * 2;
|
|
14820
|
+
h = Math.ceil(h / 2) * 2;
|
|
14821
|
+
}
|
|
14822
|
+
return {
|
|
14823
|
+
width: w,
|
|
14824
|
+
height: h,
|
|
15041
14825
|
top: containerRect.top,
|
|
15042
14826
|
left: containerRect.left
|
|
15043
|
-
}
|
|
15044
|
-
|
|
15045
|
-
);
|
|
14827
|
+
};
|
|
14828
|
+
}, [width, height, containerRect, forceEven]);
|
|
15046
14829
|
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15047
14830
|
hasInitialSizeRef.current = true;
|
|
15048
14831
|
}
|
|
@@ -15082,23 +14865,26 @@ function CanvasImpl({
|
|
|
15082
14865
|
async function run() {
|
|
15083
14866
|
if (!effectActiveRef.current || !root.current) return;
|
|
15084
14867
|
await root.current.configure({
|
|
14868
|
+
id,
|
|
14869
|
+
primaryCanvas,
|
|
14870
|
+
scheduler,
|
|
15085
14871
|
gl,
|
|
15086
14872
|
renderer,
|
|
15087
14873
|
scene,
|
|
15088
14874
|
events,
|
|
15089
14875
|
shadows,
|
|
15090
|
-
linear,
|
|
15091
|
-
flat,
|
|
15092
|
-
legacy,
|
|
15093
14876
|
orthographic,
|
|
15094
14877
|
frameloop,
|
|
15095
14878
|
dpr,
|
|
15096
14879
|
performance,
|
|
15097
14880
|
raycaster,
|
|
15098
14881
|
camera,
|
|
14882
|
+
autoUpdateFrustum,
|
|
14883
|
+
occlusion,
|
|
15099
14884
|
size: effectiveSize,
|
|
15100
14885
|
// Store size props for reset functionality
|
|
15101
14886
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14887
|
+
forceEven,
|
|
15102
14888
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15103
14889
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15104
14890
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15122,7 +14908,10 @@ function CanvasImpl({
|
|
|
15122
14908
|
});
|
|
15123
14909
|
if (!effectActiveRef.current || !root.current) return;
|
|
15124
14910
|
root.current.render(
|
|
15125
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14911
|
+
/* @__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: [
|
|
14912
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
14913
|
+
children ?? null
|
|
14914
|
+
] }) }) })
|
|
15126
14915
|
);
|
|
15127
14916
|
}
|
|
15128
14917
|
run();
|
|
@@ -15149,20 +14938,15 @@ function CanvasImpl({
|
|
|
15149
14938
|
const canvas = canvasRef.current;
|
|
15150
14939
|
if (!canvas) return;
|
|
15151
14940
|
const handleHMR = () => {
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
rootEntry
|
|
15155
|
-
|
|
15156
|
-
uniforms: {},
|
|
15157
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15158
|
-
}));
|
|
15159
|
-
}
|
|
14941
|
+
queueMicrotask(() => {
|
|
14942
|
+
const rootEntry = _roots.get(canvas);
|
|
14943
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
14944
|
+
});
|
|
15160
14945
|
};
|
|
15161
14946
|
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
14947
|
const hot = undefined;
|
|
15163
14948
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15164
|
-
return () => hot.
|
|
15165
|
-
});
|
|
14949
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15166
14950
|
}
|
|
15167
14951
|
if (typeof module !== "undefined" && module.hot) {
|
|
15168
14952
|
const hot = module.hot;
|
|
@@ -15185,7 +14969,16 @@ function CanvasImpl({
|
|
|
15185
14969
|
...style
|
|
15186
14970
|
},
|
|
15187
14971
|
...props,
|
|
15188
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14972
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14973
|
+
"canvas",
|
|
14974
|
+
{
|
|
14975
|
+
ref: canvasRef,
|
|
14976
|
+
id,
|
|
14977
|
+
className: "r3f-canvas",
|
|
14978
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
14979
|
+
children: fallback
|
|
14980
|
+
}
|
|
14981
|
+
) })
|
|
15189
14982
|
}
|
|
15190
14983
|
);
|
|
15191
14984
|
}
|
|
@@ -15258,6 +15051,48 @@ let ScopedStore = _ScopedStore;
|
|
|
15258
15051
|
function createScopedStore(data) {
|
|
15259
15052
|
return new ScopedStore(data);
|
|
15260
15053
|
}
|
|
15054
|
+
function createLazyCreatorState(state) {
|
|
15055
|
+
let _uniforms = null;
|
|
15056
|
+
let _nodes = null;
|
|
15057
|
+
let _buffers = null;
|
|
15058
|
+
let _gpuStorage = null;
|
|
15059
|
+
return Object.create(state, {
|
|
15060
|
+
uniforms: {
|
|
15061
|
+
get() {
|
|
15062
|
+
return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
|
|
15063
|
+
}
|
|
15064
|
+
},
|
|
15065
|
+
nodes: {
|
|
15066
|
+
get() {
|
|
15067
|
+
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15068
|
+
}
|
|
15069
|
+
},
|
|
15070
|
+
buffers: {
|
|
15071
|
+
get() {
|
|
15072
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15073
|
+
}
|
|
15074
|
+
},
|
|
15075
|
+
gpuStorage: {
|
|
15076
|
+
get() {
|
|
15077
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15078
|
+
}
|
|
15079
|
+
}
|
|
15080
|
+
});
|
|
15081
|
+
}
|
|
15082
|
+
|
|
15083
|
+
function resolvePrimary(local) {
|
|
15084
|
+
return local.getState().primaryStore ?? local;
|
|
15085
|
+
}
|
|
15086
|
+
function usePrimaryStore() {
|
|
15087
|
+
const local = useStore();
|
|
15088
|
+
const primary = local.getState().primaryStore;
|
|
15089
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15090
|
+
}
|
|
15091
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15092
|
+
const local = useStore();
|
|
15093
|
+
const primary = resolvePrimary(local);
|
|
15094
|
+
return primary(selector, equalityFn);
|
|
15095
|
+
}
|
|
15261
15096
|
|
|
15262
15097
|
function addTexture(set, key, value) {
|
|
15263
15098
|
set((state) => {
|
|
@@ -15298,6 +15133,27 @@ function createTextureOperations(set) {
|
|
|
15298
15133
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15299
15134
|
};
|
|
15300
15135
|
}
|
|
15136
|
+
function extractTSLValue(value) {
|
|
15137
|
+
if (value === null || value === void 0) return value;
|
|
15138
|
+
if (typeof value !== "object") return value;
|
|
15139
|
+
const node = value;
|
|
15140
|
+
if (!node.isNode) return value;
|
|
15141
|
+
if (node.isConstNode) {
|
|
15142
|
+
return node.value;
|
|
15143
|
+
}
|
|
15144
|
+
if ("value" in node) {
|
|
15145
|
+
let extractedValue = node.value;
|
|
15146
|
+
if (typeof node.traverse === "function") {
|
|
15147
|
+
node.traverse((n) => {
|
|
15148
|
+
if (n.isConstNode) {
|
|
15149
|
+
extractedValue = n.value;
|
|
15150
|
+
}
|
|
15151
|
+
});
|
|
15152
|
+
}
|
|
15153
|
+
return extractedValue;
|
|
15154
|
+
}
|
|
15155
|
+
return value;
|
|
15156
|
+
}
|
|
15301
15157
|
function vectorize(inObject) {
|
|
15302
15158
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15303
15159
|
if (typeof inObject === "string") {
|
|
@@ -15310,9 +15166,16 @@ function vectorize(inObject) {
|
|
|
15310
15166
|
}
|
|
15311
15167
|
if (typeof inObject !== "object") return inObject;
|
|
15312
15168
|
const obj = inObject;
|
|
15169
|
+
if (obj.isNode) {
|
|
15170
|
+
return extractTSLValue(inObject);
|
|
15171
|
+
}
|
|
15313
15172
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15314
15173
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15315
15174
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15175
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15176
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15177
|
+
return new webgpu.Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15178
|
+
}
|
|
15316
15179
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15317
15180
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15318
15181
|
return new webgpu.Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15336,7 +15199,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15336
15199
|
|
|
15337
15200
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15338
15201
|
function useUniforms(creatorOrScope, scope) {
|
|
15339
|
-
const store =
|
|
15202
|
+
const store = usePrimaryStore();
|
|
15340
15203
|
const removeUniforms2 = React.useCallback(
|
|
15341
15204
|
(names, targetScope) => {
|
|
15342
15205
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15375,17 +15238,14 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15375
15238
|
const rebuildUniforms = React.useCallback(
|
|
15376
15239
|
(targetScope) => {
|
|
15377
15240
|
store.setState((state) => {
|
|
15378
|
-
let newUniforms =
|
|
15241
|
+
let newUniforms = {};
|
|
15379
15242
|
if (targetScope && targetScope !== "root") {
|
|
15380
15243
|
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15381
15244
|
newUniforms = rest;
|
|
15382
15245
|
} else if (targetScope === "root") {
|
|
15383
|
-
newUniforms = {};
|
|
15384
15246
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15385
15247
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15386
15248
|
}
|
|
15387
|
-
} else {
|
|
15388
|
-
newUniforms = {};
|
|
15389
15249
|
}
|
|
15390
15250
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15391
15251
|
});
|
|
@@ -15393,20 +15253,26 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15393
15253
|
[store]
|
|
15394
15254
|
);
|
|
15395
15255
|
const inputForMemoization = React.useMemo(() => {
|
|
15256
|
+
let raw = creatorOrScope;
|
|
15396
15257
|
if (is.fun(creatorOrScope)) {
|
|
15397
|
-
const
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15258
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15259
|
+
raw = creatorOrScope(wrappedState);
|
|
15260
|
+
}
|
|
15261
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15262
|
+
const normalized = {};
|
|
15263
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15264
|
+
normalized[key] = vectorize(value);
|
|
15265
|
+
}
|
|
15266
|
+
return normalized;
|
|
15404
15267
|
}
|
|
15405
|
-
return
|
|
15268
|
+
return raw;
|
|
15406
15269
|
}, [creatorOrScope, store]);
|
|
15407
15270
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15408
15271
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15409
|
-
const storeUniforms =
|
|
15272
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15273
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15274
|
+
const readerDep = isReader ? storeUniforms : null;
|
|
15275
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15410
15276
|
const uniforms = React.useMemo(() => {
|
|
15411
15277
|
if (memoizedInput === void 0) {
|
|
15412
15278
|
return storeUniforms;
|
|
@@ -15461,28 +15327,20 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15461
15327
|
}
|
|
15462
15328
|
}
|
|
15463
15329
|
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
|
-
]);
|
|
15330
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15471
15331
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15472
15332
|
}
|
|
15473
15333
|
function rebuildAllUniforms(store, scope) {
|
|
15334
|
+
store = store.getState().primaryStore ?? store;
|
|
15474
15335
|
store.setState((state) => {
|
|
15475
|
-
let newUniforms =
|
|
15336
|
+
let newUniforms = {};
|
|
15476
15337
|
if (scope && scope !== "root") {
|
|
15477
15338
|
const { [scope]: _, ...rest } = state.uniforms;
|
|
15478
15339
|
newUniforms = rest;
|
|
15479
15340
|
} else if (scope === "root") {
|
|
15480
|
-
newUniforms = {};
|
|
15481
15341
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15482
15342
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15483
15343
|
}
|
|
15484
|
-
} else {
|
|
15485
|
-
newUniforms = {};
|
|
15486
15344
|
}
|
|
15487
15345
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15488
15346
|
});
|
|
@@ -15550,15 +15408,17 @@ function isSameThreeType(a, b) {
|
|
|
15550
15408
|
}
|
|
15551
15409
|
|
|
15552
15410
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15411
|
+
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
15553
15412
|
function useUniform(name, value) {
|
|
15554
15413
|
const store = useStore();
|
|
15414
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15555
15415
|
return React.useMemo(() => {
|
|
15556
15416
|
const state = store.getState();
|
|
15557
15417
|
const set = store.setState;
|
|
15558
15418
|
const existing = state.uniforms[name];
|
|
15559
15419
|
if (existing && isUniformNode(existing)) {
|
|
15560
|
-
if (value !== void 0) {
|
|
15561
|
-
existing.value = value;
|
|
15420
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15421
|
+
existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
|
|
15562
15422
|
}
|
|
15563
15423
|
return existing;
|
|
15564
15424
|
}
|
|
@@ -15567,7 +15427,24 @@ function useUniform(name, value) {
|
|
|
15567
15427
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15568
15428
|
);
|
|
15569
15429
|
}
|
|
15570
|
-
|
|
15430
|
+
if (isUniformNode(value)) {
|
|
15431
|
+
const node2 = value;
|
|
15432
|
+
if (typeof node2.setName === "function") {
|
|
15433
|
+
node2.setName(name);
|
|
15434
|
+
}
|
|
15435
|
+
set((s) => ({
|
|
15436
|
+
uniforms: { ...s.uniforms, [name]: node2 }
|
|
15437
|
+
}));
|
|
15438
|
+
return node2;
|
|
15439
|
+
}
|
|
15440
|
+
let node;
|
|
15441
|
+
if (isTSLNode$1(value)) {
|
|
15442
|
+
node = tsl.uniform(value);
|
|
15443
|
+
} else if (typeof value === "string") {
|
|
15444
|
+
node = tsl.uniform(new webgpu.Color(value));
|
|
15445
|
+
} else {
|
|
15446
|
+
node = tsl.uniform(value);
|
|
15447
|
+
}
|
|
15571
15448
|
if (typeof node.setName === "function") {
|
|
15572
15449
|
node.setName(name);
|
|
15573
15450
|
}
|
|
@@ -15578,12 +15455,12 @@ function useUniform(name, value) {
|
|
|
15578
15455
|
}
|
|
15579
15456
|
}));
|
|
15580
15457
|
return node;
|
|
15581
|
-
}, [store, name]);
|
|
15458
|
+
}, [store, name, hmrVersion]);
|
|
15582
15459
|
}
|
|
15583
15460
|
|
|
15584
15461
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15585
15462
|
function useNodes(creatorOrScope, scope) {
|
|
15586
|
-
const store =
|
|
15463
|
+
const store = usePrimaryStore();
|
|
15587
15464
|
const removeNodes2 = React.useCallback(
|
|
15588
15465
|
(names, targetScope) => {
|
|
15589
15466
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15640,8 +15517,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15640
15517
|
[store]
|
|
15641
15518
|
);
|
|
15642
15519
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15643
|
-
const storeNodes =
|
|
15644
|
-
const hmrVersion =
|
|
15520
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15521
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15522
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15523
|
+
const readerDep = isReader ? storeNodes : null;
|
|
15524
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15645
15525
|
const nodes = React.useMemo(() => {
|
|
15646
15526
|
if (creatorOrScope === void 0) {
|
|
15647
15527
|
return storeNodes;
|
|
@@ -15654,11 +15534,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15654
15534
|
const state = store.getState();
|
|
15655
15535
|
const set = store.setState;
|
|
15656
15536
|
const creator = creatorOrScope;
|
|
15657
|
-
const wrappedState =
|
|
15658
|
-
...state,
|
|
15659
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15660
|
-
nodes: createScopedStore(state.nodes)
|
|
15661
|
-
};
|
|
15537
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15662
15538
|
const created = creator(wrappedState);
|
|
15663
15539
|
const result = {};
|
|
15664
15540
|
let hasNewNodes = false;
|
|
@@ -15668,7 +15544,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15668
15544
|
if (currentScope[name]) {
|
|
15669
15545
|
result[name] = currentScope[name];
|
|
15670
15546
|
} else {
|
|
15671
|
-
|
|
15547
|
+
node.setName?.(`${scope}.${name}`);
|
|
15672
15548
|
result[name] = node;
|
|
15673
15549
|
hasNewNodes = true;
|
|
15674
15550
|
}
|
|
@@ -15688,7 +15564,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15688
15564
|
if (existing && isTSLNode(existing)) {
|
|
15689
15565
|
result[name] = existing;
|
|
15690
15566
|
} else {
|
|
15691
|
-
|
|
15567
|
+
node.setName?.(name);
|
|
15692
15568
|
result[name] = node;
|
|
15693
15569
|
hasNewNodes = true;
|
|
15694
15570
|
}
|
|
@@ -15697,18 +15573,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15697
15573
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15698
15574
|
}
|
|
15699
15575
|
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
|
-
]);
|
|
15576
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15709
15577
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15710
15578
|
}
|
|
15711
15579
|
function rebuildAllNodes(store, scope) {
|
|
15580
|
+
store = store.getState().primaryStore ?? store;
|
|
15712
15581
|
store.setState((state) => {
|
|
15713
15582
|
let newNodes = state.nodes;
|
|
15714
15583
|
if (scope && scope !== "root") {
|
|
@@ -15753,22 +15622,364 @@ function clearRootNodes(set) {
|
|
|
15753
15622
|
});
|
|
15754
15623
|
}
|
|
15755
15624
|
function useLocalNodes(creator) {
|
|
15756
|
-
const store =
|
|
15757
|
-
const uniforms =
|
|
15758
|
-
const nodes =
|
|
15759
|
-
const textures =
|
|
15625
|
+
const store = usePrimaryStore();
|
|
15626
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15627
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15628
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15629
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15760
15630
|
return React.useMemo(() => {
|
|
15761
|
-
const
|
|
15762
|
-
const wrappedState = {
|
|
15763
|
-
...state,
|
|
15764
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15765
|
-
nodes: createScopedStore(state.nodes)
|
|
15766
|
-
};
|
|
15631
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15767
15632
|
return creator(wrappedState);
|
|
15768
|
-
}, [store, creator, uniforms, nodes, textures]);
|
|
15633
|
+
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
15634
|
+
}
|
|
15635
|
+
|
|
15636
|
+
const isBufferLike = (value) => {
|
|
15637
|
+
if (value === null || typeof value !== "object") return false;
|
|
15638
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15639
|
+
if ("isBufferAttribute" in value) return true;
|
|
15640
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15641
|
+
return false;
|
|
15642
|
+
};
|
|
15643
|
+
const disposeBuffer = (buffer) => {
|
|
15644
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15645
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15646
|
+
buffer.dispose();
|
|
15647
|
+
}
|
|
15648
|
+
};
|
|
15649
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15650
|
+
const store = usePrimaryStore();
|
|
15651
|
+
const removeBuffers = React.useCallback(
|
|
15652
|
+
(names, targetScope) => {
|
|
15653
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15654
|
+
store.setState((state) => {
|
|
15655
|
+
if (targetScope) {
|
|
15656
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15657
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15658
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15659
|
+
}
|
|
15660
|
+
const buffers2 = { ...state.buffers };
|
|
15661
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15662
|
+
return { buffers: buffers2 };
|
|
15663
|
+
});
|
|
15664
|
+
},
|
|
15665
|
+
[store]
|
|
15666
|
+
);
|
|
15667
|
+
const clearBuffers = React.useCallback(
|
|
15668
|
+
(targetScope) => {
|
|
15669
|
+
store.setState((state) => {
|
|
15670
|
+
if (targetScope && targetScope !== "root") {
|
|
15671
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15672
|
+
return { buffers: rest };
|
|
15673
|
+
}
|
|
15674
|
+
if (targetScope === "root") {
|
|
15675
|
+
const buffers2 = {};
|
|
15676
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15677
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15678
|
+
}
|
|
15679
|
+
return { buffers: buffers2 };
|
|
15680
|
+
}
|
|
15681
|
+
return { buffers: {} };
|
|
15682
|
+
});
|
|
15683
|
+
},
|
|
15684
|
+
[store]
|
|
15685
|
+
);
|
|
15686
|
+
const rebuildBuffers = React.useCallback(
|
|
15687
|
+
(targetScope) => {
|
|
15688
|
+
store.setState((state) => {
|
|
15689
|
+
let newBuffers = state.buffers;
|
|
15690
|
+
if (targetScope && targetScope !== "root") {
|
|
15691
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15692
|
+
newBuffers = rest;
|
|
15693
|
+
} else if (targetScope === "root") {
|
|
15694
|
+
newBuffers = {};
|
|
15695
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15696
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15697
|
+
}
|
|
15698
|
+
} else {
|
|
15699
|
+
newBuffers = {};
|
|
15700
|
+
}
|
|
15701
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15702
|
+
});
|
|
15703
|
+
},
|
|
15704
|
+
[store]
|
|
15705
|
+
);
|
|
15706
|
+
const disposeBuffers = React.useCallback(
|
|
15707
|
+
(names, targetScope) => {
|
|
15708
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15709
|
+
const state = store.getState();
|
|
15710
|
+
for (const name of nameArray) {
|
|
15711
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15712
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15713
|
+
disposeBuffer(buffer);
|
|
15714
|
+
}
|
|
15715
|
+
}
|
|
15716
|
+
removeBuffers(names, targetScope);
|
|
15717
|
+
},
|
|
15718
|
+
[store, removeBuffers]
|
|
15719
|
+
);
|
|
15720
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15721
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15722
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15723
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15724
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15725
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15726
|
+
const buffers = React.useMemo(() => {
|
|
15727
|
+
if (creatorOrScope === void 0) {
|
|
15728
|
+
return storeBuffers;
|
|
15729
|
+
}
|
|
15730
|
+
if (typeof creatorOrScope === "string") {
|
|
15731
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15732
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15733
|
+
return {};
|
|
15734
|
+
}
|
|
15735
|
+
const state = store.getState();
|
|
15736
|
+
const set = store.setState;
|
|
15737
|
+
const creator = creatorOrScope;
|
|
15738
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15739
|
+
const created = creator(wrappedState);
|
|
15740
|
+
const result = {};
|
|
15741
|
+
let hasNewBuffers = false;
|
|
15742
|
+
if (scope) {
|
|
15743
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15744
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15745
|
+
if (currentScope[name]) {
|
|
15746
|
+
result[name] = currentScope[name];
|
|
15747
|
+
} else {
|
|
15748
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15749
|
+
buffer.setName(`${scope}.${name}`);
|
|
15750
|
+
}
|
|
15751
|
+
result[name] = buffer;
|
|
15752
|
+
hasNewBuffers = true;
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
if (hasNewBuffers) {
|
|
15756
|
+
set((s) => ({
|
|
15757
|
+
buffers: {
|
|
15758
|
+
...s.buffers,
|
|
15759
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15760
|
+
}
|
|
15761
|
+
}));
|
|
15762
|
+
}
|
|
15763
|
+
return result;
|
|
15764
|
+
}
|
|
15765
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15766
|
+
const existing = state.buffers[name];
|
|
15767
|
+
if (existing && isBufferLike(existing)) {
|
|
15768
|
+
result[name] = existing;
|
|
15769
|
+
} else {
|
|
15770
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15771
|
+
buffer.setName(name);
|
|
15772
|
+
}
|
|
15773
|
+
result[name] = buffer;
|
|
15774
|
+
hasNewBuffers = true;
|
|
15775
|
+
}
|
|
15776
|
+
}
|
|
15777
|
+
if (hasNewBuffers) {
|
|
15778
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15779
|
+
}
|
|
15780
|
+
return result;
|
|
15781
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15782
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15783
|
+
}
|
|
15784
|
+
function rebuildAllBuffers(store, scope) {
|
|
15785
|
+
store = store.getState().primaryStore ?? store;
|
|
15786
|
+
store.setState((state) => {
|
|
15787
|
+
let newBuffers = state.buffers;
|
|
15788
|
+
if (scope && scope !== "root") {
|
|
15789
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15790
|
+
newBuffers = rest;
|
|
15791
|
+
} else if (scope === "root") {
|
|
15792
|
+
newBuffers = {};
|
|
15793
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15794
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15795
|
+
}
|
|
15796
|
+
} else {
|
|
15797
|
+
newBuffers = {};
|
|
15798
|
+
}
|
|
15799
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15800
|
+
});
|
|
15801
|
+
}
|
|
15802
|
+
|
|
15803
|
+
const isStorageLike = (value) => {
|
|
15804
|
+
if (value === null || typeof value !== "object") return false;
|
|
15805
|
+
if ("isTexture" in value) return true;
|
|
15806
|
+
if ("isData3DTexture" in value) return true;
|
|
15807
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15808
|
+
return false;
|
|
15809
|
+
};
|
|
15810
|
+
const disposeStorage = (storage) => {
|
|
15811
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15812
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15813
|
+
storage.dispose();
|
|
15814
|
+
}
|
|
15815
|
+
};
|
|
15816
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15817
|
+
const store = usePrimaryStore();
|
|
15818
|
+
const removeStorage = React.useCallback(
|
|
15819
|
+
(names, targetScope) => {
|
|
15820
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15821
|
+
store.setState((state) => {
|
|
15822
|
+
if (targetScope) {
|
|
15823
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15824
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15825
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15826
|
+
}
|
|
15827
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15828
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15829
|
+
return { gpuStorage: gpuStorage2 };
|
|
15830
|
+
});
|
|
15831
|
+
},
|
|
15832
|
+
[store]
|
|
15833
|
+
);
|
|
15834
|
+
const clearStorage = React.useCallback(
|
|
15835
|
+
(targetScope) => {
|
|
15836
|
+
store.setState((state) => {
|
|
15837
|
+
if (targetScope && targetScope !== "root") {
|
|
15838
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15839
|
+
return { gpuStorage: rest };
|
|
15840
|
+
}
|
|
15841
|
+
if (targetScope === "root") {
|
|
15842
|
+
const gpuStorage2 = {};
|
|
15843
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15844
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15845
|
+
}
|
|
15846
|
+
return { gpuStorage: gpuStorage2 };
|
|
15847
|
+
}
|
|
15848
|
+
return { gpuStorage: {} };
|
|
15849
|
+
});
|
|
15850
|
+
},
|
|
15851
|
+
[store]
|
|
15852
|
+
);
|
|
15853
|
+
const rebuildStorage = React.useCallback(
|
|
15854
|
+
(targetScope) => {
|
|
15855
|
+
store.setState((state) => {
|
|
15856
|
+
let newStorage = state.gpuStorage;
|
|
15857
|
+
if (targetScope && targetScope !== "root") {
|
|
15858
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15859
|
+
newStorage = rest;
|
|
15860
|
+
} else if (targetScope === "root") {
|
|
15861
|
+
newStorage = {};
|
|
15862
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15863
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15864
|
+
}
|
|
15865
|
+
} else {
|
|
15866
|
+
newStorage = {};
|
|
15867
|
+
}
|
|
15868
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15869
|
+
});
|
|
15870
|
+
},
|
|
15871
|
+
[store]
|
|
15872
|
+
);
|
|
15873
|
+
const disposeStorageFn = React.useCallback(
|
|
15874
|
+
(names, targetScope) => {
|
|
15875
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15876
|
+
const state = store.getState();
|
|
15877
|
+
for (const name of nameArray) {
|
|
15878
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15879
|
+
if (storage && isStorageLike(storage)) {
|
|
15880
|
+
disposeStorage(storage);
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
15883
|
+
removeStorage(names, targetScope);
|
|
15884
|
+
},
|
|
15885
|
+
[store, removeStorage]
|
|
15886
|
+
);
|
|
15887
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15888
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15889
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15890
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15891
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15892
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15893
|
+
const gpuStorage = React.useMemo(() => {
|
|
15894
|
+
if (creatorOrScope === void 0) {
|
|
15895
|
+
return storeStorage;
|
|
15896
|
+
}
|
|
15897
|
+
if (typeof creatorOrScope === "string") {
|
|
15898
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15899
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15900
|
+
return {};
|
|
15901
|
+
}
|
|
15902
|
+
const state = store.getState();
|
|
15903
|
+
const set = store.setState;
|
|
15904
|
+
const creator = creatorOrScope;
|
|
15905
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15906
|
+
const created = creator(wrappedState);
|
|
15907
|
+
const result = {};
|
|
15908
|
+
let hasNewStorage = false;
|
|
15909
|
+
if (scope) {
|
|
15910
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15911
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15912
|
+
if (currentScope[name]) {
|
|
15913
|
+
result[name] = currentScope[name];
|
|
15914
|
+
} else {
|
|
15915
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15916
|
+
storage.setName(`${scope}.${name}`);
|
|
15917
|
+
}
|
|
15918
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15919
|
+
storage.name = `${scope}.${name}`;
|
|
15920
|
+
}
|
|
15921
|
+
result[name] = storage;
|
|
15922
|
+
hasNewStorage = true;
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
if (hasNewStorage) {
|
|
15926
|
+
set((s) => ({
|
|
15927
|
+
gpuStorage: {
|
|
15928
|
+
...s.gpuStorage,
|
|
15929
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
15930
|
+
}
|
|
15931
|
+
}));
|
|
15932
|
+
}
|
|
15933
|
+
return result;
|
|
15934
|
+
}
|
|
15935
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15936
|
+
const existing = state.gpuStorage[name];
|
|
15937
|
+
if (existing && isStorageLike(existing)) {
|
|
15938
|
+
result[name] = existing;
|
|
15939
|
+
} else {
|
|
15940
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15941
|
+
storage.setName(name);
|
|
15942
|
+
}
|
|
15943
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15944
|
+
storage.name = name;
|
|
15945
|
+
}
|
|
15946
|
+
result[name] = storage;
|
|
15947
|
+
hasNewStorage = true;
|
|
15948
|
+
}
|
|
15949
|
+
}
|
|
15950
|
+
if (hasNewStorage) {
|
|
15951
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
15952
|
+
}
|
|
15953
|
+
return result;
|
|
15954
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15955
|
+
return {
|
|
15956
|
+
...gpuStorage,
|
|
15957
|
+
removeStorage,
|
|
15958
|
+
clearStorage,
|
|
15959
|
+
rebuildStorage,
|
|
15960
|
+
disposeStorage: disposeStorageFn
|
|
15961
|
+
};
|
|
15962
|
+
}
|
|
15963
|
+
function rebuildAllStorage(store, scope) {
|
|
15964
|
+
store = store.getState().primaryStore ?? store;
|
|
15965
|
+
store.setState((state) => {
|
|
15966
|
+
let newStorage = state.gpuStorage;
|
|
15967
|
+
if (scope && scope !== "root") {
|
|
15968
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
15969
|
+
newStorage = rest;
|
|
15970
|
+
} else if (scope === "root") {
|
|
15971
|
+
newStorage = {};
|
|
15972
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15973
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15974
|
+
}
|
|
15975
|
+
} else {
|
|
15976
|
+
newStorage = {};
|
|
15977
|
+
}
|
|
15978
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15979
|
+
});
|
|
15769
15980
|
}
|
|
15770
15981
|
|
|
15771
|
-
function
|
|
15982
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15772
15983
|
const store = useStore();
|
|
15773
15984
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15774
15985
|
const callbacksRanRef = React.useRef(false);
|
|
@@ -15787,7 +15998,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15787
15998
|
}, [store]);
|
|
15788
15999
|
const reset = React.useCallback(() => {
|
|
15789
16000
|
store.setState({
|
|
15790
|
-
|
|
16001
|
+
renderPipeline: null,
|
|
15791
16002
|
passes: {}
|
|
15792
16003
|
});
|
|
15793
16004
|
callbacksRanRef.current = false;
|
|
@@ -15800,13 +16011,13 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15800
16011
|
}, []);
|
|
15801
16012
|
React.useLayoutEffect(() => {
|
|
15802
16013
|
if (isLegacy) {
|
|
15803
|
-
throw new Error("
|
|
16014
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15804
16015
|
}
|
|
15805
16016
|
if (!renderer || !scene || !camera) return;
|
|
15806
16017
|
const state = store.getState();
|
|
15807
16018
|
const set = store.setState;
|
|
15808
16019
|
try {
|
|
15809
|
-
let pp = state.
|
|
16020
|
+
let pp = state.renderPipeline;
|
|
15810
16021
|
let currentPasses = { ...state.passes };
|
|
15811
16022
|
let justCreatedPP = false;
|
|
15812
16023
|
if (!pp) {
|
|
@@ -15823,7 +16034,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15823
16034
|
}
|
|
15824
16035
|
currentPasses.scenePass = scenePass;
|
|
15825
16036
|
if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
|
|
15826
|
-
set({
|
|
16037
|
+
set({ renderPipeline: pp, passes: currentPasses });
|
|
15827
16038
|
const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
|
|
15828
16039
|
if (shouldRunCallbacks) {
|
|
15829
16040
|
if (setupCBRef.current) {
|
|
@@ -15845,33 +16056,41 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15845
16056
|
callbacksRanRef.current = true;
|
|
15846
16057
|
}
|
|
15847
16058
|
} catch (error) {
|
|
15848
|
-
console.error("[
|
|
16059
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15849
16060
|
}
|
|
15850
16061
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15851
16062
|
const passes = useThree((s) => s.passes);
|
|
15852
|
-
const
|
|
16063
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15853
16064
|
return {
|
|
15854
16065
|
passes,
|
|
15855
|
-
|
|
16066
|
+
renderPipeline,
|
|
15856
16067
|
clearPasses,
|
|
15857
16068
|
reset,
|
|
15858
16069
|
rebuild,
|
|
15859
|
-
// isReady indicates if
|
|
15860
|
-
isReady:
|
|
16070
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16071
|
+
isReady: renderPipeline !== null
|
|
15861
16072
|
};
|
|
15862
16073
|
}
|
|
15863
16074
|
|
|
15864
16075
|
extend(THREE);
|
|
15865
16076
|
|
|
16077
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
16078
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15866
16079
|
exports.Block = Block;
|
|
15867
16080
|
exports.Canvas = Canvas;
|
|
16081
|
+
exports.Environment = Environment;
|
|
16082
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
16083
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
16084
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15868
16085
|
exports.ErrorBoundary = ErrorBoundary;
|
|
16086
|
+
exports.FROM_REF = FROM_REF;
|
|
15869
16087
|
exports.IsObject = IsObject;
|
|
16088
|
+
exports.ONCE = ONCE;
|
|
16089
|
+
exports.Portal = Portal;
|
|
15870
16090
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15871
16091
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15872
16092
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15873
16093
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15874
|
-
exports.Scheduler = Scheduler;
|
|
15875
16094
|
exports.Texture = Texture;
|
|
15876
16095
|
exports._roots = _roots;
|
|
15877
16096
|
exports.act = act;
|
|
@@ -15902,35 +16121,49 @@ exports.events = createPointerEvents;
|
|
|
15902
16121
|
exports.extend = extend;
|
|
15903
16122
|
exports.findInitialRoot = findInitialRoot;
|
|
15904
16123
|
exports.flushSync = flushSync;
|
|
16124
|
+
exports.fromRef = fromRef;
|
|
15905
16125
|
exports.getInstanceProps = getInstanceProps;
|
|
16126
|
+
exports.getPrimary = getPrimary;
|
|
16127
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15906
16128
|
exports.getRootState = getRootState;
|
|
15907
|
-
exports.getScheduler = getScheduler;
|
|
15908
16129
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15909
16130
|
exports.hasConstructor = hasConstructor;
|
|
16131
|
+
exports.hasPrimary = hasPrimary;
|
|
15910
16132
|
exports.invalidate = invalidate;
|
|
15911
16133
|
exports.invalidateInstance = invalidateInstance;
|
|
15912
16134
|
exports.is = is;
|
|
15913
16135
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15914
16136
|
exports.isCopyable = isCopyable;
|
|
16137
|
+
exports.isFromRef = isFromRef;
|
|
15915
16138
|
exports.isObject3D = isObject3D;
|
|
16139
|
+
exports.isOnce = isOnce;
|
|
15916
16140
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15917
16141
|
exports.isRef = isRef;
|
|
15918
16142
|
exports.isRenderer = isRenderer;
|
|
15919
16143
|
exports.isTexture = isTexture;
|
|
15920
16144
|
exports.isVectorLike = isVectorLike;
|
|
16145
|
+
exports.once = once;
|
|
15921
16146
|
exports.prepare = prepare;
|
|
16147
|
+
exports.presetsObj = presetsObj;
|
|
16148
|
+
exports.rebuildAllBuffers = rebuildAllBuffers;
|
|
15922
16149
|
exports.rebuildAllNodes = rebuildAllNodes;
|
|
16150
|
+
exports.rebuildAllStorage = rebuildAllStorage;
|
|
15923
16151
|
exports.rebuildAllUniforms = rebuildAllUniforms;
|
|
15924
16152
|
exports.reconciler = reconciler;
|
|
16153
|
+
exports.registerPrimary = registerPrimary;
|
|
15925
16154
|
exports.removeInteractivity = removeInteractivity;
|
|
15926
16155
|
exports.removeNodes = removeNodes;
|
|
15927
16156
|
exports.removeUniforms = removeUniforms;
|
|
15928
16157
|
exports.resolve = resolve;
|
|
15929
16158
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
16159
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15930
16160
|
exports.updateCamera = updateCamera;
|
|
15931
16161
|
exports.updateFrustum = updateFrustum;
|
|
15932
16162
|
exports.useBridge = useBridge;
|
|
16163
|
+
exports.useBuffers = useBuffers;
|
|
16164
|
+
exports.useEnvironment = useEnvironment;
|
|
15933
16165
|
exports.useFrame = useFrame;
|
|
16166
|
+
exports.useGPUStorage = useGPUStorage;
|
|
15934
16167
|
exports.useGraph = useGraph;
|
|
15935
16168
|
exports.useInstanceHandle = useInstanceHandle;
|
|
15936
16169
|
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
|
@@ -15938,7 +16171,7 @@ exports.useLoader = useLoader;
|
|
|
15938
16171
|
exports.useLocalNodes = useLocalNodes;
|
|
15939
16172
|
exports.useMutableCallback = useMutableCallback;
|
|
15940
16173
|
exports.useNodes = useNodes;
|
|
15941
|
-
exports.
|
|
16174
|
+
exports.useRenderPipeline = useRenderPipeline;
|
|
15942
16175
|
exports.useRenderTarget = useRenderTarget;
|
|
15943
16176
|
exports.useStore = useStore;
|
|
15944
16177
|
exports.useTexture = useTexture;
|
|
@@ -15946,3 +16179,4 @@ exports.useTextures = useTextures;
|
|
|
15946
16179
|
exports.useThree = useThree;
|
|
15947
16180
|
exports.useUniform = useUniform;
|
|
15948
16181
|
exports.useUniforms = useUniforms;
|
|
16182
|
+
exports.waitForPrimary = waitForPrimary;
|