@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.0706a92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/index.cjs +1274 -1405
- package/dist/index.d.cts +1888 -1713
- package/dist/index.d.mts +1888 -1713
- package/dist/index.d.ts +1888 -1713
- package/dist/index.mjs +1235 -1384
- package/dist/legacy.cjs +1231 -1386
- package/dist/legacy.d.cts +1889 -1714
- package/dist/legacy.d.mts +1889 -1714
- package/dist/legacy.d.ts +1889 -1714
- package/dist/legacy.mjs +1192 -1365
- package/dist/webgpu/index.cjs +1832 -1516
- package/dist/webgpu/index.d.cts +2082 -1737
- package/dist/webgpu/index.d.mts +2082 -1737
- package/dist/webgpu/index.d.ts +2082 -1737
- package/dist/webgpu/index.mjs +1790 -1496
- package/package.json +4 -1
- package/readme.md +244 -318
package/dist/index.cjs
CHANGED
|
@@ -7,8 +7,15 @@ const jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
const React = require('react');
|
|
8
8
|
const useMeasure = require('react-use-measure');
|
|
9
9
|
const itsFine = require('its-fine');
|
|
10
|
+
const fiber = require('@react-three/fiber');
|
|
11
|
+
const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
|
|
12
|
+
const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
|
|
13
|
+
const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
|
|
14
|
+
const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
|
|
15
|
+
const gainmapJs = require('@monogrid/gainmap-js');
|
|
10
16
|
const Tb = require('scheduler');
|
|
11
17
|
const traditional = require('zustand/traditional');
|
|
18
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
12
19
|
const suspendReact = require('suspend-react');
|
|
13
20
|
|
|
14
21
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -57,9 +64,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
57
64
|
WebGLRenderer: three.WebGLRenderer
|
|
58
65
|
}, [webgpu__namespace]);
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
68
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
69
|
+
function registerPrimary(id, renderer, store) {
|
|
70
|
+
if (primaryRegistry.has(id)) {
|
|
71
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
72
|
+
}
|
|
73
|
+
const entry = { renderer, store };
|
|
74
|
+
primaryRegistry.set(id, entry);
|
|
75
|
+
const subscribers = pendingSubscribers.get(id);
|
|
76
|
+
if (subscribers) {
|
|
77
|
+
subscribers.forEach((callback) => callback(entry));
|
|
78
|
+
pendingSubscribers.delete(id);
|
|
79
|
+
}
|
|
80
|
+
return () => {
|
|
81
|
+
const currentEntry = primaryRegistry.get(id);
|
|
82
|
+
if (currentEntry?.renderer === renderer) {
|
|
83
|
+
primaryRegistry.delete(id);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getPrimary(id) {
|
|
88
|
+
return primaryRegistry.get(id);
|
|
89
|
+
}
|
|
90
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
91
|
+
const existing = primaryRegistry.get(id);
|
|
92
|
+
if (existing) {
|
|
93
|
+
return Promise.resolve(existing);
|
|
94
|
+
}
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const timeoutId = setTimeout(() => {
|
|
97
|
+
const subscribers = pendingSubscribers.get(id);
|
|
98
|
+
if (subscribers) {
|
|
99
|
+
const index = subscribers.indexOf(callback);
|
|
100
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
101
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
102
|
+
}
|
|
103
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
104
|
+
}, timeout);
|
|
105
|
+
const callback = (entry) => {
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
resolve(entry);
|
|
108
|
+
};
|
|
109
|
+
if (!pendingSubscribers.has(id)) {
|
|
110
|
+
pendingSubscribers.set(id, []);
|
|
111
|
+
}
|
|
112
|
+
pendingSubscribers.get(id).push(callback);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function hasPrimary(id) {
|
|
116
|
+
return primaryRegistry.has(id);
|
|
117
|
+
}
|
|
118
|
+
function unregisterPrimary(id) {
|
|
119
|
+
primaryRegistry.delete(id);
|
|
120
|
+
}
|
|
121
|
+
function getPrimaryIds() {
|
|
122
|
+
return Array.from(primaryRegistry.keys());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const presetsObj = {
|
|
126
|
+
apartment: "lebombo_1k.hdr",
|
|
127
|
+
city: "potsdamer_platz_1k.hdr",
|
|
128
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
129
|
+
forest: "forest_slope_1k.hdr",
|
|
130
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
131
|
+
night: "dikhololo_night_1k.hdr",
|
|
132
|
+
park: "rooitou_park_1k.hdr",
|
|
133
|
+
studio: "studio_small_03_1k.hdr",
|
|
134
|
+
sunset: "venice_sunset_1k.hdr",
|
|
135
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
139
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
140
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
141
|
+
function useEnvironment({
|
|
142
|
+
files = defaultFiles,
|
|
143
|
+
path = "",
|
|
144
|
+
preset = void 0,
|
|
145
|
+
colorSpace = void 0,
|
|
146
|
+
extensions
|
|
147
|
+
} = {}) {
|
|
148
|
+
if (preset) {
|
|
149
|
+
validatePreset(preset);
|
|
150
|
+
files = presetsObj[preset];
|
|
151
|
+
path = CUBEMAP_ROOT;
|
|
152
|
+
}
|
|
153
|
+
const multiFile = isArray(files);
|
|
154
|
+
const { extension, isCubemap } = getExtension(files);
|
|
155
|
+
const loader = getLoader$1(extension);
|
|
156
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
157
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
158
|
+
React.useLayoutEffect(() => {
|
|
159
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
160
|
+
function clearGainmapTexture() {
|
|
161
|
+
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
162
|
+
}
|
|
163
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
164
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
165
|
+
const loaderResult = fiber.useLoader(
|
|
166
|
+
loader,
|
|
167
|
+
multiFile ? [files] : files,
|
|
168
|
+
(loader2) => {
|
|
169
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
170
|
+
loader2.setRenderer?.(renderer);
|
|
171
|
+
}
|
|
172
|
+
loader2.setPath?.(path);
|
|
173
|
+
if (extensions) extensions(loader2);
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
let texture = multiFile ? (
|
|
177
|
+
// @ts-ignore
|
|
178
|
+
loaderResult[0]
|
|
179
|
+
) : loaderResult;
|
|
180
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
181
|
+
texture = texture.renderTarget?.texture;
|
|
182
|
+
}
|
|
183
|
+
texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
|
|
184
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
185
|
+
return texture;
|
|
186
|
+
}
|
|
187
|
+
const preloadDefaultOptions = {
|
|
188
|
+
files: defaultFiles,
|
|
189
|
+
path: "",
|
|
190
|
+
preset: void 0,
|
|
191
|
+
extensions: void 0
|
|
192
|
+
};
|
|
193
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
194
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
195
|
+
let { files, path = "" } = options;
|
|
196
|
+
const { preset, extensions } = options;
|
|
197
|
+
if (preset) {
|
|
198
|
+
validatePreset(preset);
|
|
199
|
+
files = presetsObj[preset];
|
|
200
|
+
path = CUBEMAP_ROOT;
|
|
201
|
+
}
|
|
202
|
+
const { extension } = getExtension(files);
|
|
203
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
204
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
205
|
+
}
|
|
206
|
+
const loader = getLoader$1(extension);
|
|
207
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
208
|
+
fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
209
|
+
loader2.setPath?.(path);
|
|
210
|
+
if (extensions) extensions(loader2);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
const clearDefaultOptins = {
|
|
214
|
+
files: defaultFiles,
|
|
215
|
+
preset: void 0
|
|
216
|
+
};
|
|
217
|
+
useEnvironment.clear = (clearOptions) => {
|
|
218
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
219
|
+
let { files } = options;
|
|
220
|
+
const { preset } = options;
|
|
221
|
+
if (preset) {
|
|
222
|
+
validatePreset(preset);
|
|
223
|
+
files = presetsObj[preset];
|
|
224
|
+
}
|
|
225
|
+
const { extension } = getExtension(files);
|
|
226
|
+
const loader = getLoader$1(extension);
|
|
227
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
228
|
+
fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
|
|
229
|
+
};
|
|
230
|
+
function validatePreset(preset) {
|
|
231
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
232
|
+
}
|
|
233
|
+
function getExtension(files) {
|
|
234
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
235
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
236
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
237
|
+
const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
|
|
238
|
+
return { extension, isCubemap, isGainmap };
|
|
239
|
+
}
|
|
240
|
+
function getLoader$1(extension) {
|
|
241
|
+
const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
|
|
242
|
+
return loader;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
246
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
247
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
248
|
+
sceneProps = {
|
|
249
|
+
backgroundBlurriness: 0,
|
|
250
|
+
backgroundIntensity: 1,
|
|
251
|
+
backgroundRotation: [0, 0, 0],
|
|
252
|
+
environmentIntensity: 1,
|
|
253
|
+
environmentRotation: [0, 0, 0],
|
|
254
|
+
...sceneProps
|
|
255
|
+
};
|
|
256
|
+
const target = resolveScene(scene || defaultScene);
|
|
257
|
+
const oldbg = target.background;
|
|
258
|
+
const oldenv = target.environment;
|
|
259
|
+
const oldSceneProps = {
|
|
260
|
+
// @ts-ignore
|
|
261
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
262
|
+
// @ts-ignore
|
|
263
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
environmentIntensity: target.environmentIntensity,
|
|
268
|
+
// @ts-ignore
|
|
269
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
270
|
+
};
|
|
271
|
+
if (background !== "only") target.environment = texture;
|
|
272
|
+
if (background) target.background = texture;
|
|
273
|
+
fiber.applyProps(target, sceneProps);
|
|
274
|
+
return () => {
|
|
275
|
+
if (background !== "only") target.environment = oldenv;
|
|
276
|
+
if (background) target.background = oldbg;
|
|
277
|
+
fiber.applyProps(target, oldSceneProps);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
281
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
282
|
+
React__namespace.useLayoutEffect(() => {
|
|
283
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function EnvironmentCube({
|
|
288
|
+
background = false,
|
|
289
|
+
scene,
|
|
290
|
+
blur,
|
|
291
|
+
backgroundBlurriness,
|
|
292
|
+
backgroundIntensity,
|
|
293
|
+
backgroundRotation,
|
|
294
|
+
environmentIntensity,
|
|
295
|
+
environmentRotation,
|
|
296
|
+
...rest
|
|
297
|
+
}) {
|
|
298
|
+
const texture = useEnvironment(rest);
|
|
299
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
300
|
+
React__namespace.useLayoutEffect(() => {
|
|
301
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
302
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
303
|
+
backgroundIntensity,
|
|
304
|
+
backgroundRotation,
|
|
305
|
+
environmentIntensity,
|
|
306
|
+
environmentRotation
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
React__namespace.useEffect(() => {
|
|
310
|
+
return () => {
|
|
311
|
+
texture.dispose();
|
|
312
|
+
};
|
|
313
|
+
}, [texture]);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function EnvironmentPortal({
|
|
317
|
+
children,
|
|
318
|
+
near = 0.1,
|
|
319
|
+
far = 1e3,
|
|
320
|
+
resolution = 256,
|
|
321
|
+
frames = 1,
|
|
322
|
+
map,
|
|
323
|
+
background = false,
|
|
324
|
+
blur,
|
|
325
|
+
backgroundBlurriness,
|
|
326
|
+
backgroundIntensity,
|
|
327
|
+
backgroundRotation,
|
|
328
|
+
environmentIntensity,
|
|
329
|
+
environmentRotation,
|
|
330
|
+
scene,
|
|
331
|
+
files,
|
|
332
|
+
path,
|
|
333
|
+
preset = void 0,
|
|
334
|
+
extensions
|
|
335
|
+
}) {
|
|
336
|
+
const gl = fiber.useThree((state) => state.gl);
|
|
337
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
338
|
+
const camera = React__namespace.useRef(null);
|
|
339
|
+
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
340
|
+
const fbo = React__namespace.useMemo(() => {
|
|
341
|
+
const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
|
|
342
|
+
fbo2.texture.type = webgpu.HalfFloatType;
|
|
343
|
+
return fbo2;
|
|
344
|
+
}, [resolution]);
|
|
345
|
+
React__namespace.useEffect(() => {
|
|
346
|
+
return () => {
|
|
347
|
+
fbo.dispose();
|
|
348
|
+
};
|
|
349
|
+
}, [fbo]);
|
|
350
|
+
React__namespace.useLayoutEffect(() => {
|
|
351
|
+
if (frames === 1) {
|
|
352
|
+
const autoClear = gl.autoClear;
|
|
353
|
+
gl.autoClear = true;
|
|
354
|
+
camera.current.update(gl, virtualScene);
|
|
355
|
+
gl.autoClear = autoClear;
|
|
356
|
+
}
|
|
357
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
358
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
359
|
+
backgroundIntensity,
|
|
360
|
+
backgroundRotation,
|
|
361
|
+
environmentIntensity,
|
|
362
|
+
environmentRotation
|
|
363
|
+
});
|
|
364
|
+
}, [
|
|
365
|
+
children,
|
|
366
|
+
virtualScene,
|
|
367
|
+
fbo.texture,
|
|
368
|
+
scene,
|
|
369
|
+
defaultScene,
|
|
370
|
+
background,
|
|
371
|
+
frames,
|
|
372
|
+
gl,
|
|
373
|
+
blur,
|
|
374
|
+
backgroundBlurriness,
|
|
375
|
+
backgroundIntensity,
|
|
376
|
+
backgroundRotation,
|
|
377
|
+
environmentIntensity,
|
|
378
|
+
environmentRotation
|
|
379
|
+
]);
|
|
380
|
+
let count = 1;
|
|
381
|
+
fiber.useFrame(() => {
|
|
382
|
+
if (frames === Infinity || count < frames) {
|
|
383
|
+
const autoClear = gl.autoClear;
|
|
384
|
+
gl.autoClear = true;
|
|
385
|
+
camera.current.update(gl, virtualScene);
|
|
386
|
+
gl.autoClear = autoClear;
|
|
387
|
+
count++;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
392
|
+
children,
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
394
|
+
files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
395
|
+
] }),
|
|
396
|
+
virtualScene
|
|
397
|
+
) });
|
|
398
|
+
}
|
|
399
|
+
function EnvironmentGround(props) {
|
|
400
|
+
const textureDefault = useEnvironment(props);
|
|
401
|
+
const texture = props.map || textureDefault;
|
|
402
|
+
React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
|
|
403
|
+
React__namespace.useEffect(() => {
|
|
404
|
+
return () => {
|
|
405
|
+
textureDefault.dispose();
|
|
406
|
+
};
|
|
407
|
+
}, [textureDefault]);
|
|
408
|
+
const height = props.ground?.height ?? 15;
|
|
409
|
+
const radius = props.ground?.radius ?? 60;
|
|
410
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
411
|
+
const args = React__namespace.useMemo(
|
|
412
|
+
() => [texture, height, radius],
|
|
413
|
+
[texture, height, radius]
|
|
414
|
+
);
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
|
|
418
|
+
] });
|
|
419
|
+
}
|
|
420
|
+
function EnvironmentColor({ color, scene }) {
|
|
421
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
422
|
+
React__namespace.useLayoutEffect(() => {
|
|
423
|
+
if (color === void 0) return;
|
|
424
|
+
const target = resolveScene(scene || defaultScene);
|
|
425
|
+
const oldBg = target.background;
|
|
426
|
+
target.background = new webgpu.Color(color);
|
|
427
|
+
return () => {
|
|
428
|
+
target.background = oldBg;
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function EnvironmentDualSource(props) {
|
|
434
|
+
const { backgroundFiles, ...envProps } = props;
|
|
435
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
function Environment(props) {
|
|
441
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
442
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
|
|
443
|
+
}
|
|
444
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
|
|
446
|
+
}
|
|
447
|
+
return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var __defProp = Object.defineProperty;
|
|
451
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
452
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
63
453
|
const act = React__namespace["act"];
|
|
64
454
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
65
455
|
function useMutableCallback(fn) {
|
|
@@ -91,7 +481,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
91
481
|
return _a = class extends React__namespace.Component {
|
|
92
482
|
constructor() {
|
|
93
483
|
super(...arguments);
|
|
94
|
-
__publicField
|
|
484
|
+
__publicField(this, "state", { error: false });
|
|
95
485
|
}
|
|
96
486
|
componentDidCatch(err) {
|
|
97
487
|
this.props.set(err);
|
|
@@ -99,7 +489,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
99
489
|
render() {
|
|
100
490
|
return this.state.error ? null : this.props.children;
|
|
101
491
|
}
|
|
102
|
-
}, __publicField
|
|
492
|
+
}, __publicField(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
103
493
|
})();
|
|
104
494
|
|
|
105
495
|
const is = {
|
|
@@ -285,7 +675,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
285
675
|
let occlusionSetupPromise = null;
|
|
286
676
|
function enableOcclusion(store) {
|
|
287
677
|
const state = store.getState();
|
|
288
|
-
const { internal, renderer
|
|
678
|
+
const { internal, renderer } = state;
|
|
289
679
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
290
680
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
291
681
|
if (!hasOcclusionSupport) {
|
|
@@ -393,6 +783,17 @@ function unregisterVisibility(store, object) {
|
|
|
393
783
|
internal.visibilityRegistry.delete(object.uuid);
|
|
394
784
|
internal.occlusionCache.delete(object);
|
|
395
785
|
}
|
|
786
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
787
|
+
const { internal } = store.getState();
|
|
788
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
789
|
+
if (!entry) return false;
|
|
790
|
+
entry.handlers = handlers;
|
|
791
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
792
|
+
object.occlusionTest = true;
|
|
793
|
+
enableOcclusion(store);
|
|
794
|
+
}
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
396
797
|
function checkVisibility(state) {
|
|
397
798
|
const { internal, camera } = state;
|
|
398
799
|
const registry = internal.visibilityRegistry;
|
|
@@ -448,6 +849,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
448
849
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
449
850
|
}
|
|
450
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
|
+
|
|
451
868
|
const RESERVED_PROPS = [
|
|
452
869
|
"children",
|
|
453
870
|
"key",
|
|
@@ -518,7 +935,7 @@ function getMemoizedPrototype(root) {
|
|
|
518
935
|
ctor = new root.constructor();
|
|
519
936
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
520
937
|
}
|
|
521
|
-
} catch
|
|
938
|
+
} catch {
|
|
522
939
|
}
|
|
523
940
|
return ctor;
|
|
524
941
|
}
|
|
@@ -564,6 +981,22 @@ function applyProps(object, props) {
|
|
|
564
981
|
continue;
|
|
565
982
|
}
|
|
566
983
|
if (value === void 0) continue;
|
|
984
|
+
if (isFromRef(value)) continue;
|
|
985
|
+
if (isOnce(value)) {
|
|
986
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
987
|
+
if (instance) {
|
|
988
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
989
|
+
instance.appliedOnce.add(prop);
|
|
990
|
+
}
|
|
991
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
992
|
+
const args = value[ONCE];
|
|
993
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
994
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
995
|
+
} else if (args !== true && args.length > 0) {
|
|
996
|
+
targetRoot[targetKey] = args[0];
|
|
997
|
+
}
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
567
1000
|
let { root, key, target } = resolve(object, prop);
|
|
568
1001
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
569
1002
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -584,9 +1017,25 @@ function applyProps(object, props) {
|
|
|
584
1017
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
585
1018
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
586
1019
|
else target.set(value);
|
|
1020
|
+
} else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1021
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1022
|
+
const uniforms = root.uniforms;
|
|
1023
|
+
const nextUniforms = value;
|
|
1024
|
+
for (const name in nextUniforms) {
|
|
1025
|
+
const uniform = nextUniforms[name];
|
|
1026
|
+
const targetUniform = uniforms[name];
|
|
1027
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1028
|
+
else {
|
|
1029
|
+
const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
|
|
1030
|
+
uniforms[name] = nextUniform;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
587
1033
|
} else {
|
|
588
1034
|
root[key] = value;
|
|
589
|
-
if (
|
|
1035
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1036
|
+
root.needsUpdate = true;
|
|
1037
|
+
}
|
|
1038
|
+
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
|
|
590
1039
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
591
1040
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
592
1041
|
}
|
|
@@ -599,6 +1048,9 @@ function applyProps(object, props) {
|
|
|
599
1048
|
if (instance.eventCount && object2.raycast !== null) {
|
|
600
1049
|
rootState.internal.interaction.push(object2);
|
|
601
1050
|
}
|
|
1051
|
+
}
|
|
1052
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1053
|
+
const object2 = instance.object;
|
|
602
1054
|
const root = findInitialRoot(instance);
|
|
603
1055
|
const visibilityHandlers = {
|
|
604
1056
|
onFramed: instance.handlers.onFramed,
|
|
@@ -606,7 +1058,9 @@ function applyProps(object, props) {
|
|
|
606
1058
|
onVisible: instance.handlers.onVisible
|
|
607
1059
|
};
|
|
608
1060
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
609
|
-
|
|
1061
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1062
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1063
|
+
}
|
|
610
1064
|
} else {
|
|
611
1065
|
unregisterVisibility(root, object2);
|
|
612
1066
|
}
|
|
@@ -619,38 +1073,60 @@ function applyProps(object, props) {
|
|
|
619
1073
|
return object;
|
|
620
1074
|
}
|
|
621
1075
|
|
|
1076
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1077
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1078
|
+
function getPointerState(internal, pointerId) {
|
|
1079
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1080
|
+
if (!state) {
|
|
1081
|
+
state = {
|
|
1082
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1083
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1084
|
+
initialClick: [0, 0],
|
|
1085
|
+
initialHits: []
|
|
1086
|
+
};
|
|
1087
|
+
internal.pointerMap.set(pointerId, state);
|
|
1088
|
+
}
|
|
1089
|
+
return state;
|
|
1090
|
+
}
|
|
1091
|
+
function getPointerId(event) {
|
|
1092
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1093
|
+
}
|
|
622
1094
|
function makeId(event) {
|
|
623
1095
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
624
1096
|
}
|
|
625
|
-
function releaseInternalPointerCapture(
|
|
626
|
-
const
|
|
1097
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1098
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1099
|
+
if (!pointerState) return;
|
|
1100
|
+
const captureData = pointerState.captured.get(obj);
|
|
627
1101
|
if (captureData) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
capturedMap.delete(pointerId);
|
|
631
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
632
|
-
}
|
|
1102
|
+
pointerState.captured.delete(obj);
|
|
1103
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
633
1104
|
}
|
|
634
1105
|
}
|
|
635
1106
|
function removeInteractivity(store, object) {
|
|
636
1107
|
const { internal } = store.getState();
|
|
637
1108
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1109
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1110
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1111
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1112
|
+
if (value.eventObject === object || value.object === object) {
|
|
1113
|
+
pointerState.hovered.delete(key);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
if (pointerState.captured.has(object)) {
|
|
1117
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
642
1118
|
}
|
|
643
|
-
}
|
|
644
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
645
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
646
|
-
});
|
|
1119
|
+
}
|
|
647
1120
|
unregisterVisibility(store, object);
|
|
648
1121
|
}
|
|
649
1122
|
function createEvents(store) {
|
|
650
|
-
function calculateDistance(event) {
|
|
1123
|
+
function calculateDistance(event, pointerId) {
|
|
651
1124
|
const { internal } = store.getState();
|
|
652
|
-
const
|
|
653
|
-
|
|
1125
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1126
|
+
if (!pointerState) return 0;
|
|
1127
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1128
|
+
const dx = event.offsetX - initialX;
|
|
1129
|
+
const dy = event.offsetY - initialY;
|
|
654
1130
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
655
1131
|
}
|
|
656
1132
|
function filterPointerEvents(objects) {
|
|
@@ -686,6 +1162,15 @@ function createEvents(store) {
|
|
|
686
1162
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
687
1163
|
}
|
|
688
1164
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1165
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1166
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1167
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1168
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1169
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1170
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1171
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
689
1174
|
const aState = getRootState(a.object);
|
|
690
1175
|
const bState = getRootState(b.object);
|
|
691
1176
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -707,9 +1192,13 @@ function createEvents(store) {
|
|
|
707
1192
|
eventObject = eventObject.parent;
|
|
708
1193
|
}
|
|
709
1194
|
}
|
|
710
|
-
if ("pointerId" in event
|
|
711
|
-
|
|
712
|
-
|
|
1195
|
+
if ("pointerId" in event) {
|
|
1196
|
+
const pointerId = event.pointerId;
|
|
1197
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1198
|
+
if (pointerState?.captured.size) {
|
|
1199
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1200
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1201
|
+
}
|
|
713
1202
|
}
|
|
714
1203
|
}
|
|
715
1204
|
return intersections;
|
|
@@ -722,27 +1211,25 @@ function createEvents(store) {
|
|
|
722
1211
|
if (state) {
|
|
723
1212
|
const { raycaster, pointer, camera, internal } = state;
|
|
724
1213
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
725
|
-
const hasPointerCapture = (id) =>
|
|
1214
|
+
const hasPointerCapture = (id) => {
|
|
1215
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1216
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1217
|
+
};
|
|
726
1218
|
const setPointerCapture = (id) => {
|
|
727
1219
|
const captureData = { intersection: hit, target: event.target };
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
} else {
|
|
731
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
732
|
-
}
|
|
1220
|
+
const pointerState = getPointerState(internal, id);
|
|
1221
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
733
1222
|
event.target.setPointerCapture(id);
|
|
734
1223
|
};
|
|
735
1224
|
const releasePointerCapture = (id) => {
|
|
736
|
-
|
|
737
|
-
if (captures) {
|
|
738
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
739
|
-
}
|
|
1225
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
740
1226
|
};
|
|
741
1227
|
const extractEventProps = {};
|
|
742
1228
|
for (const prop in event) {
|
|
743
1229
|
const property = event[prop];
|
|
744
1230
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
745
1231
|
}
|
|
1232
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
746
1233
|
const raycastEvent = {
|
|
747
1234
|
...hit,
|
|
748
1235
|
...extractEventProps,
|
|
@@ -753,18 +1240,19 @@ function createEvents(store) {
|
|
|
753
1240
|
unprojectedPoint,
|
|
754
1241
|
ray: raycaster.ray,
|
|
755
1242
|
camera,
|
|
1243
|
+
pointerId: eventPointerId,
|
|
756
1244
|
// Hijack stopPropagation, which just sets a flag
|
|
757
1245
|
stopPropagation() {
|
|
758
|
-
const
|
|
1246
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
759
1247
|
if (
|
|
760
1248
|
// ...if this pointer hasn't been captured
|
|
761
|
-
!
|
|
762
|
-
|
|
1249
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1250
|
+
pointerState.captured.has(hit.eventObject)
|
|
763
1251
|
) {
|
|
764
1252
|
raycastEvent.stopped = localState.stopped = true;
|
|
765
|
-
if (
|
|
1253
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
766
1254
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
767
|
-
cancelPointer([...higher, hit]);
|
|
1255
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
768
1256
|
}
|
|
769
1257
|
}
|
|
770
1258
|
},
|
|
@@ -780,15 +1268,18 @@ function createEvents(store) {
|
|
|
780
1268
|
}
|
|
781
1269
|
return intersections;
|
|
782
1270
|
}
|
|
783
|
-
function cancelPointer(intersections) {
|
|
1271
|
+
function cancelPointer(intersections, pointerId) {
|
|
784
1272
|
const { internal } = store.getState();
|
|
785
|
-
|
|
1273
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1274
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1275
|
+
if (!pointerState) return;
|
|
1276
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
786
1277
|
if (!intersections.length || !intersections.find(
|
|
787
1278
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
788
1279
|
)) {
|
|
789
1280
|
const eventObject = hoveredObj.eventObject;
|
|
790
1281
|
const instance = eventObject.__r3f;
|
|
791
|
-
|
|
1282
|
+
pointerState.hovered.delete(hoveredId);
|
|
792
1283
|
if (instance?.eventCount) {
|
|
793
1284
|
const handlers = instance.handlers;
|
|
794
1285
|
const data = { ...hoveredObj, intersections };
|
|
@@ -817,41 +1308,118 @@ function createEvents(store) {
|
|
|
817
1308
|
instance?.handlers.onDropMissed?.(event);
|
|
818
1309
|
}
|
|
819
1310
|
}
|
|
1311
|
+
function cleanupPointer(pointerId) {
|
|
1312
|
+
const { internal } = store.getState();
|
|
1313
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1314
|
+
if (pointerState) {
|
|
1315
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1316
|
+
const eventObject = hoveredObj.eventObject;
|
|
1317
|
+
const instance = eventObject.__r3f;
|
|
1318
|
+
if (instance?.eventCount) {
|
|
1319
|
+
const handlers = instance.handlers;
|
|
1320
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1321
|
+
handlers.onPointerOut?.(data);
|
|
1322
|
+
handlers.onPointerLeave?.(data);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
internal.pointerMap.delete(pointerId);
|
|
1326
|
+
}
|
|
1327
|
+
internal.pointerDirty.delete(pointerId);
|
|
1328
|
+
}
|
|
1329
|
+
function processDeferredPointer(event, pointerId) {
|
|
1330
|
+
const state = store.getState();
|
|
1331
|
+
const { internal } = state;
|
|
1332
|
+
if (!state.events.enabled) return;
|
|
1333
|
+
const filter = filterPointerEvents;
|
|
1334
|
+
const hits = intersect(event, filter);
|
|
1335
|
+
cancelPointer(hits, pointerId);
|
|
1336
|
+
function onIntersect(data) {
|
|
1337
|
+
const eventObject = data.eventObject;
|
|
1338
|
+
const instance = eventObject.__r3f;
|
|
1339
|
+
if (!instance?.eventCount) return;
|
|
1340
|
+
const handlers = instance.handlers;
|
|
1341
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1342
|
+
const id = makeId(data);
|
|
1343
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1344
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1345
|
+
if (!hoveredItem) {
|
|
1346
|
+
pointerState.hovered.set(id, data);
|
|
1347
|
+
handlers.onPointerOver?.(data);
|
|
1348
|
+
handlers.onPointerEnter?.(data);
|
|
1349
|
+
} else if (hoveredItem.stopped) {
|
|
1350
|
+
data.stopPropagation();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
handlers.onPointerMove?.(data);
|
|
1354
|
+
}
|
|
1355
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1356
|
+
}
|
|
820
1357
|
function handlePointer(name) {
|
|
821
1358
|
switch (name) {
|
|
822
1359
|
case "onPointerLeave":
|
|
823
|
-
case "onPointerCancel":
|
|
824
1360
|
case "onDragLeave":
|
|
825
1361
|
return () => cancelPointer([]);
|
|
1362
|
+
// Global cancel of these events
|
|
1363
|
+
case "onPointerCancel":
|
|
1364
|
+
return (event) => {
|
|
1365
|
+
const pointerId = getPointerId(event);
|
|
1366
|
+
cleanupPointer(pointerId);
|
|
1367
|
+
};
|
|
826
1368
|
case "onLostPointerCapture":
|
|
827
1369
|
return (event) => {
|
|
828
1370
|
const { internal } = store.getState();
|
|
829
|
-
|
|
1371
|
+
const pointerId = getPointerId(event);
|
|
1372
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1373
|
+
if (pointerState?.captured.size) {
|
|
830
1374
|
requestAnimationFrame(() => {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1375
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1376
|
+
if (pointerState2?.captured.size) {
|
|
1377
|
+
pointerState2.captured.clear();
|
|
834
1378
|
}
|
|
1379
|
+
cancelPointer([], pointerId);
|
|
835
1380
|
});
|
|
836
1381
|
}
|
|
837
1382
|
};
|
|
838
1383
|
}
|
|
839
1384
|
return function handleEvent(event) {
|
|
840
1385
|
const state = store.getState();
|
|
841
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1386
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1387
|
+
const pointerId = getPointerId(event);
|
|
842
1388
|
internal.lastEvent.current = event;
|
|
843
|
-
if (!
|
|
1389
|
+
if (!events.enabled) return;
|
|
844
1390
|
const isPointerMove = name === "onPointerMove";
|
|
845
1391
|
const isDragOver = name === "onDragOver";
|
|
846
1392
|
const isDrop = name === "onDrop";
|
|
847
1393
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1394
|
+
const isPointerDown = name === "onPointerDown";
|
|
1395
|
+
const isPointerUp = name === "onPointerUp";
|
|
1396
|
+
const isWheel = name === "onWheel";
|
|
1397
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1398
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1399
|
+
events.compute?.(event, state);
|
|
1400
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1404
|
+
events.compute?.(event, state);
|
|
1405
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1409
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1410
|
+
internal.pointerDirty.delete(pointerId);
|
|
1411
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1412
|
+
}
|
|
848
1413
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
849
1414
|
const hits = intersect(event, filter);
|
|
850
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
851
|
-
if (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1415
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1416
|
+
if (isPointerDown) {
|
|
1417
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1418
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1419
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1420
|
+
}
|
|
1421
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1422
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
855
1423
|
if (isClickEvent && !hits.length) {
|
|
856
1424
|
if (delta <= 2) {
|
|
857
1425
|
pointerMissed(event, internal.interaction);
|
|
@@ -866,7 +1434,9 @@ function createEvents(store) {
|
|
|
866
1434
|
dropMissed(event, internal.interaction);
|
|
867
1435
|
if (onDropMissed) onDropMissed(event);
|
|
868
1436
|
}
|
|
869
|
-
if (isPointerMove || isDragOver)
|
|
1437
|
+
if (isPointerMove || isDragOver) {
|
|
1438
|
+
cancelPointer(hits, pointerId);
|
|
1439
|
+
}
|
|
870
1440
|
function onIntersect(data) {
|
|
871
1441
|
const eventObject = data.eventObject;
|
|
872
1442
|
const instance = eventObject.__r3f;
|
|
@@ -875,9 +1445,10 @@ function createEvents(store) {
|
|
|
875
1445
|
if (isPointerMove) {
|
|
876
1446
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
877
1447
|
const id = makeId(data);
|
|
878
|
-
const
|
|
1448
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1449
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
879
1450
|
if (!hoveredItem) {
|
|
880
|
-
|
|
1451
|
+
pointerState2.hovered.set(id, data);
|
|
881
1452
|
handlers.onPointerOver?.(data);
|
|
882
1453
|
handlers.onPointerEnter?.(data);
|
|
883
1454
|
} else if (hoveredItem.stopped) {
|
|
@@ -887,9 +1458,10 @@ function createEvents(store) {
|
|
|
887
1458
|
handlers.onPointerMove?.(data);
|
|
888
1459
|
} else if (isDragOver) {
|
|
889
1460
|
const id = makeId(data);
|
|
890
|
-
const
|
|
1461
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1462
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
891
1463
|
if (!hoveredItem) {
|
|
892
|
-
|
|
1464
|
+
pointerState2.hovered.set(id, data);
|
|
893
1465
|
handlers.onDragOverEnter?.(data);
|
|
894
1466
|
} else if (hoveredItem.stopped) {
|
|
895
1467
|
data.stopPropagation();
|
|
@@ -900,18 +1472,18 @@ function createEvents(store) {
|
|
|
900
1472
|
} else {
|
|
901
1473
|
const handler = handlers[name];
|
|
902
1474
|
if (handler) {
|
|
903
|
-
if (!isClickEvent ||
|
|
1475
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
904
1476
|
pointerMissed(
|
|
905
1477
|
event,
|
|
906
|
-
internal.interaction.filter((object) => !
|
|
1478
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
907
1479
|
);
|
|
908
1480
|
handler(data);
|
|
909
1481
|
}
|
|
910
1482
|
} else {
|
|
911
|
-
if (isClickEvent &&
|
|
1483
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
912
1484
|
pointerMissed(
|
|
913
1485
|
event,
|
|
914
|
-
internal.interaction.filter((object) => !
|
|
1486
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
915
1487
|
);
|
|
916
1488
|
}
|
|
917
1489
|
}
|
|
@@ -920,7 +1492,15 @@ function createEvents(store) {
|
|
|
920
1492
|
handleIntersects(hits, event, delta, onIntersect);
|
|
921
1493
|
};
|
|
922
1494
|
}
|
|
923
|
-
|
|
1495
|
+
function flushDeferredPointers() {
|
|
1496
|
+
const { internal, events } = store.getState();
|
|
1497
|
+
if (!events.frameTimedRaycasts) return;
|
|
1498
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1499
|
+
processDeferredPointer(event, pointerId);
|
|
1500
|
+
}
|
|
1501
|
+
internal.pointerDirty.clear();
|
|
1502
|
+
}
|
|
1503
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
924
1504
|
}
|
|
925
1505
|
const DOM_EVENTS = {
|
|
926
1506
|
onClick: ["click", false],
|
|
@@ -939,11 +1519,16 @@ const DOM_EVENTS = {
|
|
|
939
1519
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
940
1520
|
};
|
|
941
1521
|
function createPointerEvents(store) {
|
|
942
|
-
const { handlePointer } = createEvents(store);
|
|
1522
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1523
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1524
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
943
1525
|
return {
|
|
944
1526
|
priority: 1,
|
|
945
1527
|
enabled: true,
|
|
946
|
-
|
|
1528
|
+
frameTimedRaycasts: true,
|
|
1529
|
+
alwaysFireOnScroll: true,
|
|
1530
|
+
updateOnFrame: false,
|
|
1531
|
+
compute(event, state) {
|
|
947
1532
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
948
1533
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
949
1534
|
},
|
|
@@ -952,11 +1537,33 @@ function createPointerEvents(store) {
|
|
|
952
1537
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
953
1538
|
{}
|
|
954
1539
|
),
|
|
955
|
-
update: () => {
|
|
1540
|
+
update: (pointerId) => {
|
|
956
1541
|
const { events, internal } = store.getState();
|
|
957
|
-
if (
|
|
1542
|
+
if (!events.handlers) return;
|
|
1543
|
+
if (pointerId !== void 0) {
|
|
1544
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1545
|
+
if (event) {
|
|
1546
|
+
internal.pointerDirty.delete(pointerId);
|
|
1547
|
+
processDeferredPointer(event, pointerId);
|
|
1548
|
+
} else if (internal.lastEvent?.current) {
|
|
1549
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1550
|
+
}
|
|
1551
|
+
} else {
|
|
1552
|
+
flushDeferredPointers();
|
|
1553
|
+
if (internal.lastEvent?.current) {
|
|
1554
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
flush: () => {
|
|
1559
|
+
const { events, internal } = store.getState();
|
|
1560
|
+
flushDeferredPointers();
|
|
1561
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1562
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1563
|
+
}
|
|
958
1564
|
},
|
|
959
1565
|
connect: (target) => {
|
|
1566
|
+
if (!target) return;
|
|
960
1567
|
const { set, events } = store.getState();
|
|
961
1568
|
events.disconnect?.();
|
|
962
1569
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -980,6 +1587,33 @@ function createPointerEvents(store) {
|
|
|
980
1587
|
}
|
|
981
1588
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
982
1589
|
}
|
|
1590
|
+
},
|
|
1591
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1592
|
+
registerPointer: (config) => {
|
|
1593
|
+
const pointerId = nextXRPointerId++;
|
|
1594
|
+
xrPointers.set(pointerId, config);
|
|
1595
|
+
const { internal } = store.getState();
|
|
1596
|
+
getPointerState(internal, pointerId);
|
|
1597
|
+
return pointerId;
|
|
1598
|
+
},
|
|
1599
|
+
unregisterPointer: (pointerId) => {
|
|
1600
|
+
xrPointers.delete(pointerId);
|
|
1601
|
+
const { internal } = store.getState();
|
|
1602
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1603
|
+
if (pointerState) {
|
|
1604
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1605
|
+
const eventObject = hoveredObj.eventObject;
|
|
1606
|
+
const instance = eventObject.__r3f;
|
|
1607
|
+
if (instance?.eventCount) {
|
|
1608
|
+
const handlers = instance.handlers;
|
|
1609
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1610
|
+
handlers.onPointerOut?.(data);
|
|
1611
|
+
handlers.onPointerLeave?.(data);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
internal.pointerMap.delete(pointerId);
|
|
1615
|
+
}
|
|
1616
|
+
internal.pointerDirty.delete(pointerId);
|
|
983
1617
|
}
|
|
984
1618
|
};
|
|
985
1619
|
}
|
|
@@ -1041,6 +1675,14 @@ function notifyAlpha({ message, link }) {
|
|
|
1041
1675
|
}
|
|
1042
1676
|
}
|
|
1043
1677
|
|
|
1678
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1679
|
+
const INTERNAL_FIBER_MARKERS = ["packages/fiber/src", "packages/fiber/dist", "@react-three/fiber", "@react-three_fiber"];
|
|
1680
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1681
|
+
if (!stack) return false;
|
|
1682
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1683
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m)) || INTERNAL_FIBER_MARKERS.some((m) => caller.includes(m));
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1044
1686
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1045
1687
|
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
1046
1688
|
const createStore = (invalidate, advance) => {
|
|
@@ -1070,23 +1712,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1070
1712
|
set,
|
|
1071
1713
|
get,
|
|
1072
1714
|
// Mock objects that have to be configured
|
|
1715
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1716
|
+
primaryStore: null,
|
|
1073
1717
|
gl: null,
|
|
1074
1718
|
renderer: null,
|
|
1075
1719
|
camera: null,
|
|
1076
1720
|
frustum: new webgpu.Frustum(),
|
|
1077
1721
|
autoUpdateFrustum: true,
|
|
1078
1722
|
raycaster: null,
|
|
1079
|
-
events: {
|
|
1723
|
+
events: {
|
|
1724
|
+
priority: 1,
|
|
1725
|
+
enabled: true,
|
|
1726
|
+
connected: false,
|
|
1727
|
+
frameTimedRaycasts: true,
|
|
1728
|
+
alwaysFireOnScroll: true,
|
|
1729
|
+
updateOnFrame: false
|
|
1730
|
+
},
|
|
1080
1731
|
scene: null,
|
|
1081
1732
|
rootScene: null,
|
|
1082
1733
|
xr: null,
|
|
1083
1734
|
inspector: null,
|
|
1084
1735
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1085
1736
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1086
|
-
|
|
1087
|
-
linear: false,
|
|
1088
|
-
flat: false,
|
|
1089
|
-
textureColorSpace: "srgb",
|
|
1737
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1090
1738
|
isLegacy: false,
|
|
1091
1739
|
webGPUSupported: false,
|
|
1092
1740
|
isNative: false,
|
|
@@ -1144,6 +1792,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1144
1792
|
size: newSize,
|
|
1145
1793
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1146
1794
|
}));
|
|
1795
|
+
scheduler.getScheduler().invalidate();
|
|
1147
1796
|
}
|
|
1148
1797
|
}
|
|
1149
1798
|
return;
|
|
@@ -1158,6 +1807,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1158
1807
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1159
1808
|
_sizeImperative: true
|
|
1160
1809
|
}));
|
|
1810
|
+
scheduler.getScheduler().invalidate();
|
|
1161
1811
|
},
|
|
1162
1812
|
setDpr: (dpr) => set((state2) => {
|
|
1163
1813
|
const resolved = calculateDpr(dpr);
|
|
@@ -1165,14 +1815,18 @@ const createStore = (invalidate, advance) => {
|
|
|
1165
1815
|
}),
|
|
1166
1816
|
setFrameloop: (frameloop = "always") => {
|
|
1167
1817
|
set(() => ({ frameloop }));
|
|
1818
|
+
scheduler.getScheduler().frameloop = frameloop;
|
|
1168
1819
|
},
|
|
1169
1820
|
setError: (error) => set(() => ({ error })),
|
|
1170
1821
|
error: null,
|
|
1171
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1822
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1172
1823
|
uniforms: {},
|
|
1173
1824
|
nodes: {},
|
|
1825
|
+
buffers: {},
|
|
1826
|
+
gpuStorage: {},
|
|
1174
1827
|
textures: /* @__PURE__ */ new Map(),
|
|
1175
|
-
|
|
1828
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1829
|
+
renderPipeline: null,
|
|
1176
1830
|
passes: {},
|
|
1177
1831
|
_hmrVersion: 0,
|
|
1178
1832
|
_sizeImperative: false,
|
|
@@ -1181,12 +1835,16 @@ const createStore = (invalidate, advance) => {
|
|
|
1181
1835
|
internal: {
|
|
1182
1836
|
// Events
|
|
1183
1837
|
interaction: [],
|
|
1184
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1185
1838
|
subscribers: [],
|
|
1839
|
+
// Per-pointer state (new unified structure)
|
|
1840
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1841
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1842
|
+
lastEvent: React__namespace.createRef(),
|
|
1843
|
+
// Deprecated but kept for backwards compatibility
|
|
1844
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1186
1845
|
initialClick: [0, 0],
|
|
1187
1846
|
initialHits: [],
|
|
1188
1847
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1189
|
-
lastEvent: React__namespace.createRef(),
|
|
1190
1848
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1191
1849
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1192
1850
|
// Occlusion system (WebGPU only)
|
|
@@ -1225,8 +1883,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1225
1883
|
const currentState = rootStore.getState();
|
|
1226
1884
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
1227
1885
|
const stack = new Error().stack || "";
|
|
1228
|
-
|
|
1229
|
-
if (!isInternalAccess) {
|
|
1886
|
+
if (!isInternalRendererAccess(stack)) {
|
|
1230
1887
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
1231
1888
|
notifyDepreciated({
|
|
1232
1889
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -1269,13 +1926,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1269
1926
|
rootStore.subscribe(() => {
|
|
1270
1927
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1271
1928
|
const actualRenderer = internal.actualRenderer;
|
|
1929
|
+
const canvasTarget = internal.canvasTarget;
|
|
1272
1930
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1273
1931
|
oldSize = size;
|
|
1274
1932
|
oldDpr = viewport.dpr;
|
|
1275
1933
|
updateCamera(camera, size);
|
|
1276
|
-
if (
|
|
1277
|
-
|
|
1278
|
-
|
|
1934
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1935
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1936
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1937
|
+
} else {
|
|
1938
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1939
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1940
|
+
if (canvasTarget) {
|
|
1941
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1942
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1279
1945
|
}
|
|
1280
1946
|
if (camera !== oldCamera) {
|
|
1281
1947
|
oldCamera = camera;
|
|
@@ -1330,1058 +1996,32 @@ function loadingFn(extensions, onProgress) {
|
|
|
1330
1996
|
);
|
|
1331
1997
|
};
|
|
1332
1998
|
}
|
|
1333
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
1334
|
-
const
|
|
1999
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2000
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2001
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
1335
2002
|
const fn = loadingFn(extensions, onProgress);
|
|
1336
|
-
const results =
|
|
2003
|
+
const results = cacheKeys.map(
|
|
2004
|
+
(key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2005
|
+
);
|
|
1337
2006
|
return Array.isArray(input) ? results : results[0];
|
|
1338
2007
|
}
|
|
1339
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
1340
|
-
const
|
|
1341
|
-
|
|
2008
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2009
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2010
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2011
|
+
cacheKeys.forEach(
|
|
2012
|
+
(key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2013
|
+
);
|
|
1342
2014
|
};
|
|
1343
|
-
useLoader.clear = function(loader, input) {
|
|
1344
|
-
const
|
|
1345
|
-
|
|
2015
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2016
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2017
|
+
cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
|
|
1346
2018
|
};
|
|
1347
2019
|
useLoader.loader = getLoader;
|
|
1348
2020
|
|
|
1349
|
-
var __defProp$1 = Object.defineProperty;
|
|
1350
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1351
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1352
|
-
const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
|
|
1353
|
-
class PhaseGraph {
|
|
1354
|
-
constructor() {
|
|
1355
|
-
/** Ordered list of phase nodes */
|
|
1356
|
-
__publicField$1(this, "phases", []);
|
|
1357
|
-
/** Quick lookup by name */
|
|
1358
|
-
__publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1359
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1360
|
-
__publicField$1(this, "orderedNamesCache", null);
|
|
1361
|
-
this.initializeDefaultPhases();
|
|
1362
|
-
}
|
|
1363
|
-
//* Initialization --------------------------------
|
|
1364
|
-
initializeDefaultPhases() {
|
|
1365
|
-
for (const name of DEFAULT_PHASES) {
|
|
1366
|
-
const node = { name, isAutoGenerated: false };
|
|
1367
|
-
this.phases.push(node);
|
|
1368
|
-
this.phaseMap.set(name, node);
|
|
1369
|
-
}
|
|
1370
|
-
this.invalidateCache();
|
|
1371
|
-
}
|
|
1372
|
-
//* Public API --------------------------------
|
|
1373
|
-
/**
|
|
1374
|
-
* Add a named phase to the graph
|
|
1375
|
-
* @param name - Phase name (must be unique)
|
|
1376
|
-
* @param options - Position options (before or after another phase)
|
|
1377
|
-
*/
|
|
1378
|
-
addPhase(name, options = {}) {
|
|
1379
|
-
if (this.phaseMap.has(name)) {
|
|
1380
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
const { before, after } = options;
|
|
1384
|
-
const node = { name, isAutoGenerated: false };
|
|
1385
|
-
let insertIndex = this.phases.length;
|
|
1386
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1387
|
-
if (targetIndex !== -1) {
|
|
1388
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1389
|
-
} else {
|
|
1390
|
-
const constraintType = before ? "before" : "after";
|
|
1391
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1392
|
-
}
|
|
1393
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1394
|
-
this.phaseMap.set(name, node);
|
|
1395
|
-
this.invalidateCache();
|
|
1396
|
-
}
|
|
1397
|
-
/**
|
|
1398
|
-
* Get ordered list of phase names
|
|
1399
|
-
*/
|
|
1400
|
-
getOrderedPhases() {
|
|
1401
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1402
|
-
return this.orderedNamesCache;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Check if a phase exists
|
|
1406
|
-
*/
|
|
1407
|
-
hasPhase(name) {
|
|
1408
|
-
return this.phaseMap.has(name);
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Get the index of a phase (-1 if not found)
|
|
1412
|
-
*/
|
|
1413
|
-
getPhaseIndex(name) {
|
|
1414
|
-
if (!name) return -1;
|
|
1415
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1416
|
-
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1419
|
-
* Used for resolving before/after constraints.
|
|
1420
|
-
*
|
|
1421
|
-
* @param name - The phase name to ensure exists
|
|
1422
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1423
|
-
*/
|
|
1424
|
-
ensurePhase(name) {
|
|
1425
|
-
if (this.phaseMap.has(name)) return name;
|
|
1426
|
-
const node = { name, isAutoGenerated: true };
|
|
1427
|
-
this.phases.push(node);
|
|
1428
|
-
this.phaseMap.set(name, node);
|
|
1429
|
-
this.invalidateCache();
|
|
1430
|
-
return name;
|
|
1431
|
-
}
|
|
1432
|
-
/**
|
|
1433
|
-
* Resolve where a job with before/after constraints should go.
|
|
1434
|
-
* Creates auto-generated phases if needed.
|
|
1435
|
-
*
|
|
1436
|
-
* @param before - Phase(s) to run before
|
|
1437
|
-
* @param after - Phase(s) to run after
|
|
1438
|
-
* @returns The resolved phase name
|
|
1439
|
-
*/
|
|
1440
|
-
resolveConstraintPhase(before, after) {
|
|
1441
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1442
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1443
|
-
if (beforeArr.length > 0) {
|
|
1444
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1445
|
-
}
|
|
1446
|
-
if (afterArr.length > 0) {
|
|
1447
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1448
|
-
}
|
|
1449
|
-
return "update";
|
|
1450
|
-
}
|
|
1451
|
-
/**
|
|
1452
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1453
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1454
|
-
*
|
|
1455
|
-
* @param target - The target phase name to position relative to
|
|
1456
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1457
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1458
|
-
* @returns The auto-generated phase name
|
|
1459
|
-
*/
|
|
1460
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1461
|
-
const autoName = `${prefix}:${target}`;
|
|
1462
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1463
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1464
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1465
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1466
|
-
else this.phases.push(node);
|
|
1467
|
-
this.phaseMap.set(autoName, node);
|
|
1468
|
-
this.invalidateCache();
|
|
1469
|
-
return autoName;
|
|
1470
|
-
}
|
|
1471
|
-
// Internal --------------------------------
|
|
1472
|
-
invalidateCache() {
|
|
1473
|
-
this.orderedNamesCache = null;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1478
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1479
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1480
|
-
for (const phase of orderedPhases) {
|
|
1481
|
-
buckets.set(phase, []);
|
|
1482
|
-
}
|
|
1483
|
-
for (const job of jobs.values()) {
|
|
1484
|
-
if (!job.enabled) continue;
|
|
1485
|
-
let bucket = buckets.get(job.phase);
|
|
1486
|
-
if (!bucket) {
|
|
1487
|
-
bucket = [];
|
|
1488
|
-
buckets.set(job.phase, bucket);
|
|
1489
|
-
}
|
|
1490
|
-
bucket.push(job);
|
|
1491
|
-
}
|
|
1492
|
-
const sortedBuckets = [];
|
|
1493
|
-
for (const phase of orderedPhases) {
|
|
1494
|
-
const bucket = buckets.get(phase);
|
|
1495
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1496
|
-
bucket.sort((a, b) => {
|
|
1497
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1498
|
-
return a.index - b.index;
|
|
1499
|
-
});
|
|
1500
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1501
|
-
}
|
|
1502
|
-
for (const [phase, bucket] of buckets) {
|
|
1503
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1504
|
-
bucket.sort((a, b) => {
|
|
1505
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1506
|
-
return a.index - b.index;
|
|
1507
|
-
});
|
|
1508
|
-
sortedBuckets.push(bucket);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
return sortedBuckets.flat();
|
|
1512
|
-
}
|
|
1513
|
-
function hasCrossJobConstraints(bucket) {
|
|
1514
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1515
|
-
for (const job of bucket) {
|
|
1516
|
-
for (const ref of job.before) {
|
|
1517
|
-
if (jobIds.has(ref)) return true;
|
|
1518
|
-
}
|
|
1519
|
-
for (const ref of job.after) {
|
|
1520
|
-
if (jobIds.has(ref)) return true;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
return false;
|
|
1524
|
-
}
|
|
1525
|
-
function topologicalSort(jobs) {
|
|
1526
|
-
const n = jobs.length;
|
|
1527
|
-
if (n <= 1) return jobs;
|
|
1528
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1529
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1530
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1531
|
-
for (const job of jobs) {
|
|
1532
|
-
jobMap.set(job.id, job);
|
|
1533
|
-
inDegree.set(job.id, 0);
|
|
1534
|
-
adjacency.set(job.id, []);
|
|
1535
|
-
}
|
|
1536
|
-
for (const job of jobs) {
|
|
1537
|
-
for (const ref of job.before) {
|
|
1538
|
-
if (jobMap.has(ref)) {
|
|
1539
|
-
adjacency.get(job.id).push(ref);
|
|
1540
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
for (const ref of job.after) {
|
|
1544
|
-
if (jobMap.has(ref)) {
|
|
1545
|
-
adjacency.get(ref).push(job.id);
|
|
1546
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
const queue = [];
|
|
1551
|
-
for (const job of jobs) {
|
|
1552
|
-
if (inDegree.get(job.id) === 0) {
|
|
1553
|
-
queue.push(job);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
queue.sort((a, b) => {
|
|
1557
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1558
|
-
return a.index - b.index;
|
|
1559
|
-
});
|
|
1560
|
-
const result = [];
|
|
1561
|
-
while (queue.length > 0) {
|
|
1562
|
-
const job = queue.shift();
|
|
1563
|
-
result.push(job);
|
|
1564
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1565
|
-
for (const neighborId of neighbors) {
|
|
1566
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1567
|
-
inDegree.set(neighborId, newDegree);
|
|
1568
|
-
if (newDegree === 0) {
|
|
1569
|
-
const neighbor = jobMap.get(neighborId);
|
|
1570
|
-
insertSorted(queue, neighbor);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
if (result.length !== n) {
|
|
1575
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1576
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1577
|
-
for (const job of jobs) {
|
|
1578
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return result;
|
|
1582
|
-
}
|
|
1583
|
-
function insertSorted(arr, job) {
|
|
1584
|
-
let i = 0;
|
|
1585
|
-
while (i < arr.length) {
|
|
1586
|
-
const cmp = arr[i];
|
|
1587
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
i++;
|
|
1591
|
-
}
|
|
1592
|
-
arr.splice(i, 0, job);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
function shouldRun(job, now) {
|
|
1596
|
-
if (!job.enabled) return false;
|
|
1597
|
-
if (!job.fps) return true;
|
|
1598
|
-
const minInterval = 1e3 / job.fps;
|
|
1599
|
-
const lastRun = job.lastRun ?? 0;
|
|
1600
|
-
const elapsed = now - lastRun;
|
|
1601
|
-
if (elapsed < minInterval) return false;
|
|
1602
|
-
if (job.drop) {
|
|
1603
|
-
job.lastRun = now;
|
|
1604
|
-
} else {
|
|
1605
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1606
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1607
|
-
if (job.lastRun < now - minInterval) {
|
|
1608
|
-
job.lastRun = now;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return true;
|
|
1612
|
-
}
|
|
1613
|
-
function resetJobTiming(job) {
|
|
1614
|
-
job.lastRun = void 0;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
var __defProp = Object.defineProperty;
|
|
1618
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1619
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1620
|
-
const hmrData = (() => {
|
|
1621
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1622
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1623
|
-
try {
|
|
1624
|
-
return (0, eval)("import.meta.hot");
|
|
1625
|
-
} catch {
|
|
1626
|
-
return void 0;
|
|
1627
|
-
}
|
|
1628
|
-
})();
|
|
1629
|
-
const _Scheduler = class _Scheduler {
|
|
1630
|
-
//* Constructor ================================
|
|
1631
|
-
constructor() {
|
|
1632
|
-
//* Critical State ================================
|
|
1633
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1634
|
-
__publicField(this, "phaseGraph");
|
|
1635
|
-
__publicField(this, "loopState", {
|
|
1636
|
-
running: false,
|
|
1637
|
-
rafHandle: null,
|
|
1638
|
-
lastTime: null,
|
|
1639
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1640
|
-
frameCount: 0,
|
|
1641
|
-
elapsedTime: 0,
|
|
1642
|
-
createdAt: performance.now()
|
|
1643
|
-
});
|
|
1644
|
-
__publicField(this, "stoppedTime", 0);
|
|
1645
|
-
//* Private State ================================
|
|
1646
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1647
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1648
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1649
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1650
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1651
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1652
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1653
|
-
__publicField(this, "pendingFrames", 0);
|
|
1654
|
-
__publicField(this, "_frameloop", "always");
|
|
1655
|
-
//* Independent Mode & Error Handling State ================================
|
|
1656
|
-
__publicField(this, "_independent", false);
|
|
1657
|
-
__publicField(this, "errorHandler", null);
|
|
1658
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1659
|
-
//* Core Loop Execution Methods ================================
|
|
1660
|
-
/**
|
|
1661
|
-
* Main RAF loop callback.
|
|
1662
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1663
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1664
|
-
* @returns {void}
|
|
1665
|
-
* @private
|
|
1666
|
-
*/
|
|
1667
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1668
|
-
if (!this.loopState.running) return;
|
|
1669
|
-
this.executeFrame(timestamp);
|
|
1670
|
-
if (this._frameloop === "demand") {
|
|
1671
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1672
|
-
if (this.pendingFrames === 0) {
|
|
1673
|
-
this.notifyIdle(timestamp);
|
|
1674
|
-
return this.stop();
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1678
|
-
});
|
|
1679
|
-
this.phaseGraph = new PhaseGraph();
|
|
1680
|
-
}
|
|
1681
|
-
static get instance() {
|
|
1682
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1683
|
-
}
|
|
1684
|
-
static set instance(value) {
|
|
1685
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1686
|
-
}
|
|
1687
|
-
/**
|
|
1688
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1689
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1690
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1691
|
-
*/
|
|
1692
|
-
static get() {
|
|
1693
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1694
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1695
|
-
}
|
|
1696
|
-
if (!_Scheduler.instance) {
|
|
1697
|
-
_Scheduler.instance = new _Scheduler();
|
|
1698
|
-
if (hmrData?.data) {
|
|
1699
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
return _Scheduler.instance;
|
|
1703
|
-
}
|
|
1704
|
-
/**
|
|
1705
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1706
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1707
|
-
* @returns {void}
|
|
1708
|
-
*/
|
|
1709
|
-
static reset() {
|
|
1710
|
-
if (_Scheduler.instance) {
|
|
1711
|
-
_Scheduler.instance.stop();
|
|
1712
|
-
_Scheduler.instance = null;
|
|
1713
|
-
}
|
|
1714
|
-
if (hmrData?.data) {
|
|
1715
|
-
hmrData.data.scheduler = null;
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
//* Getters & Setters ================================
|
|
1719
|
-
get phases() {
|
|
1720
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1721
|
-
}
|
|
1722
|
-
get frameloop() {
|
|
1723
|
-
return this._frameloop;
|
|
1724
|
-
}
|
|
1725
|
-
set frameloop(mode) {
|
|
1726
|
-
if (this._frameloop === mode) return;
|
|
1727
|
-
const wasAlways = this._frameloop === "always";
|
|
1728
|
-
this._frameloop = mode;
|
|
1729
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1730
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1731
|
-
}
|
|
1732
|
-
get isRunning() {
|
|
1733
|
-
return this.loopState.running;
|
|
1734
|
-
}
|
|
1735
|
-
get isReady() {
|
|
1736
|
-
return this.roots.size > 0;
|
|
1737
|
-
}
|
|
1738
|
-
get independent() {
|
|
1739
|
-
return this._independent;
|
|
1740
|
-
}
|
|
1741
|
-
set independent(value) {
|
|
1742
|
-
this._independent = value;
|
|
1743
|
-
if (value) this.ensureDefaultRoot();
|
|
1744
|
-
}
|
|
1745
|
-
//* Root Management Methods ================================
|
|
1746
|
-
/**
|
|
1747
|
-
* Register a root (Canvas) with the scheduler.
|
|
1748
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1749
|
-
* @param {string} id - Unique identifier for this root
|
|
1750
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1751
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1752
|
-
*/
|
|
1753
|
-
registerRoot(id, options = {}) {
|
|
1754
|
-
if (this.roots.has(id)) {
|
|
1755
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1756
|
-
return () => this.unregisterRoot(id);
|
|
1757
|
-
}
|
|
1758
|
-
const entry = {
|
|
1759
|
-
id,
|
|
1760
|
-
getState: options.getState ?? (() => ({})),
|
|
1761
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1762
|
-
sortedJobs: [],
|
|
1763
|
-
needsRebuild: false
|
|
1764
|
-
};
|
|
1765
|
-
if (options.onError) {
|
|
1766
|
-
this.errorHandler = options.onError;
|
|
1767
|
-
}
|
|
1768
|
-
this.roots.set(id, entry);
|
|
1769
|
-
if (this.roots.size === 1) {
|
|
1770
|
-
this.notifyRootReady();
|
|
1771
|
-
if (this._frameloop === "always") this.start();
|
|
1772
|
-
}
|
|
1773
|
-
return () => this.unregisterRoot(id);
|
|
1774
|
-
}
|
|
1775
|
-
/**
|
|
1776
|
-
* Unregister a root from the scheduler.
|
|
1777
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1778
|
-
* The last root to unregister stops the RAF loop.
|
|
1779
|
-
* @param {string} id - The root ID to unregister
|
|
1780
|
-
* @returns {void}
|
|
1781
|
-
*/
|
|
1782
|
-
unregisterRoot(id) {
|
|
1783
|
-
const root = this.roots.get(id);
|
|
1784
|
-
if (!root) return;
|
|
1785
|
-
for (const jobId of root.jobs.keys()) {
|
|
1786
|
-
this.jobStateListeners.delete(jobId);
|
|
1787
|
-
}
|
|
1788
|
-
this.roots.delete(id);
|
|
1789
|
-
if (this.roots.size === 0) {
|
|
1790
|
-
this.stop();
|
|
1791
|
-
this.errorHandler = null;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Subscribe to be notified when a root becomes available.
|
|
1796
|
-
* Fires immediately if a root already exists.
|
|
1797
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1798
|
-
* @returns {() => void} Unsubscribe function
|
|
1799
|
-
*/
|
|
1800
|
-
onRootReady(callback) {
|
|
1801
|
-
if (this.roots.size > 0) {
|
|
1802
|
-
callback();
|
|
1803
|
-
return () => {
|
|
1804
|
-
};
|
|
1805
|
-
}
|
|
1806
|
-
this.rootReadyCallbacks.add(callback);
|
|
1807
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1808
|
-
}
|
|
1809
|
-
/**
|
|
1810
|
-
* Notify all registered root-ready callbacks.
|
|
1811
|
-
* Called when the first root registers.
|
|
1812
|
-
* @returns {void}
|
|
1813
|
-
* @private
|
|
1814
|
-
*/
|
|
1815
|
-
notifyRootReady() {
|
|
1816
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1817
|
-
try {
|
|
1818
|
-
cb();
|
|
1819
|
-
} catch (error) {
|
|
1820
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
this.rootReadyCallbacks.clear();
|
|
1824
|
-
}
|
|
1825
|
-
/**
|
|
1826
|
-
* Ensure a default root exists for independent mode.
|
|
1827
|
-
* Creates a minimal root with no state provider.
|
|
1828
|
-
* @returns {void}
|
|
1829
|
-
* @private
|
|
1830
|
-
*/
|
|
1831
|
-
ensureDefaultRoot() {
|
|
1832
|
-
if (!this.roots.has("__default__")) {
|
|
1833
|
-
this.registerRoot("__default__");
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
/**
|
|
1837
|
-
* Trigger error handling for job errors.
|
|
1838
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1839
|
-
* @param {Error} error - The error to handle
|
|
1840
|
-
* @returns {void}
|
|
1841
|
-
*/
|
|
1842
|
-
triggerError(error) {
|
|
1843
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1844
|
-
else console.error("[Scheduler]", error);
|
|
1845
|
-
}
|
|
1846
|
-
//* Phase Management Methods ================================
|
|
1847
|
-
/**
|
|
1848
|
-
* Add a named phase to the scheduler's execution order.
|
|
1849
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1850
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1851
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1852
|
-
* @returns {void}
|
|
1853
|
-
* @example
|
|
1854
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1855
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1856
|
-
*/
|
|
1857
|
-
addPhase(name, options) {
|
|
1858
|
-
this.phaseGraph.addPhase(name, options);
|
|
1859
|
-
for (const root of this.roots.values()) {
|
|
1860
|
-
root.needsRebuild = true;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
/**
|
|
1864
|
-
* Check if a phase exists in the scheduler.
|
|
1865
|
-
* @param {string} name - The phase name to check
|
|
1866
|
-
* @returns {boolean} True if the phase exists
|
|
1867
|
-
*/
|
|
1868
|
-
hasPhase(name) {
|
|
1869
|
-
return this.phaseGraph.hasPhase(name);
|
|
1870
|
-
}
|
|
1871
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1872
|
-
/**
|
|
1873
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1874
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1875
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1876
|
-
* @param {string} id - Unique identifier for this global job
|
|
1877
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1878
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1879
|
-
* @deprecated Use useFrame with phases instead
|
|
1880
|
-
*/
|
|
1881
|
-
registerGlobal(phase, id, callback) {
|
|
1882
|
-
const job = { id, callback };
|
|
1883
|
-
if (phase === "before") {
|
|
1884
|
-
this.globalBeforeJobs.set(id, job);
|
|
1885
|
-
} else {
|
|
1886
|
-
this.globalAfterJobs.set(id, job);
|
|
1887
|
-
}
|
|
1888
|
-
return () => {
|
|
1889
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1890
|
-
else this.globalAfterJobs.delete(id);
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1894
|
-
/**
|
|
1895
|
-
* Register an idle callback that fires when the loop stops.
|
|
1896
|
-
* Used internally by deprecated addTail API.
|
|
1897
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1898
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1899
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1900
|
-
*/
|
|
1901
|
-
onIdle(callback) {
|
|
1902
|
-
this.idleCallbacks.add(callback);
|
|
1903
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1904
|
-
}
|
|
1905
|
-
/**
|
|
1906
|
-
* Notify all registered idle callbacks.
|
|
1907
|
-
* Called when the loop stops in demand mode.
|
|
1908
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1909
|
-
* @returns {void}
|
|
1910
|
-
* @private
|
|
1911
|
-
*/
|
|
1912
|
-
notifyIdle(timestamp) {
|
|
1913
|
-
for (const cb of this.idleCallbacks) {
|
|
1914
|
-
try {
|
|
1915
|
-
cb(timestamp);
|
|
1916
|
-
} catch (error) {
|
|
1917
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
//* Job Registration & Management Methods ================================
|
|
1922
|
-
/**
|
|
1923
|
-
* Register a job (frame callback) with a specific root.
|
|
1924
|
-
* This is the core registration method used by useFrame internally.
|
|
1925
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1926
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1927
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1928
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1929
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1930
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1931
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1932
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1933
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1934
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1935
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1936
|
-
*/
|
|
1937
|
-
register(callback, options = {}) {
|
|
1938
|
-
const rootId = options.rootId;
|
|
1939
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1940
|
-
if (!root) {
|
|
1941
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1942
|
-
return () => {
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
const id = options.id ?? this.generateJobId();
|
|
1946
|
-
let phase = options.phase ?? "update";
|
|
1947
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1948
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1949
|
-
}
|
|
1950
|
-
const before = this.normalizeConstraints(options.before);
|
|
1951
|
-
const after = this.normalizeConstraints(options.after);
|
|
1952
|
-
const job = {
|
|
1953
|
-
id,
|
|
1954
|
-
callback,
|
|
1955
|
-
phase,
|
|
1956
|
-
before,
|
|
1957
|
-
after,
|
|
1958
|
-
priority: options.priority ?? 0,
|
|
1959
|
-
index: this.nextJobIndex++,
|
|
1960
|
-
fps: options.fps,
|
|
1961
|
-
drop: options.drop ?? true,
|
|
1962
|
-
enabled: options.enabled ?? true,
|
|
1963
|
-
system: options.system ?? false
|
|
1964
|
-
};
|
|
1965
|
-
if (root.jobs.has(id)) {
|
|
1966
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1967
|
-
}
|
|
1968
|
-
root.jobs.set(id, job);
|
|
1969
|
-
root.needsRebuild = true;
|
|
1970
|
-
return () => this.unregister(id, root.id);
|
|
1971
|
-
}
|
|
1972
|
-
/**
|
|
1973
|
-
* Unregister a job by its ID.
|
|
1974
|
-
* Searches all roots if rootId is not provided.
|
|
1975
|
-
* @param {string} id - The job ID to unregister
|
|
1976
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1977
|
-
* @returns {void}
|
|
1978
|
-
*/
|
|
1979
|
-
unregister(id, rootId) {
|
|
1980
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1981
|
-
if (root?.jobs.delete(id)) {
|
|
1982
|
-
root.needsRebuild = true;
|
|
1983
|
-
this.jobStateListeners.delete(id);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
/**
|
|
1987
|
-
* Update a job's options dynamically.
|
|
1988
|
-
* Searches all roots to find the job by ID.
|
|
1989
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1990
|
-
* @param {string} id - The job ID to update
|
|
1991
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1992
|
-
* @returns {void}
|
|
1993
|
-
*/
|
|
1994
|
-
updateJob(id, options) {
|
|
1995
|
-
let job;
|
|
1996
|
-
let root;
|
|
1997
|
-
for (const r of this.roots.values()) {
|
|
1998
|
-
job = r.jobs.get(id);
|
|
1999
|
-
if (job) {
|
|
2000
|
-
root = r;
|
|
2001
|
-
break;
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
if (!job || !root) return;
|
|
2005
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2006
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2007
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2008
|
-
if (options.enabled !== void 0) {
|
|
2009
|
-
const wasEnabled = job.enabled;
|
|
2010
|
-
job.enabled = options.enabled;
|
|
2011
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2012
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2013
|
-
}
|
|
2014
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2015
|
-
if (options.phase) job.phase = options.phase;
|
|
2016
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2017
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2018
|
-
root.needsRebuild = true;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
//* Job State Management Methods ================================
|
|
2022
|
-
/**
|
|
2023
|
-
* Check if a job is currently paused (disabled).
|
|
2024
|
-
* @param {string} id - The job ID to check
|
|
2025
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2026
|
-
*/
|
|
2027
|
-
isJobPaused(id) {
|
|
2028
|
-
for (const root of this.roots.values()) {
|
|
2029
|
-
const job = root.jobs.get(id);
|
|
2030
|
-
if (job) return !job.enabled;
|
|
2031
|
-
}
|
|
2032
|
-
return false;
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* Subscribe to state changes for a specific job.
|
|
2036
|
-
* Listener is called when job is paused or resumed.
|
|
2037
|
-
* @param {string} id - The job ID to subscribe to
|
|
2038
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2039
|
-
* @returns {() => void} Unsubscribe function
|
|
2040
|
-
*/
|
|
2041
|
-
subscribeJobState(id, listener) {
|
|
2042
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2043
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2044
|
-
}
|
|
2045
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2046
|
-
return () => {
|
|
2047
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2048
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2049
|
-
this.jobStateListeners.delete(id);
|
|
2050
|
-
}
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* Notify all listeners that a job's state has changed.
|
|
2055
|
-
* @param {string} id - The job ID that changed
|
|
2056
|
-
* @returns {void}
|
|
2057
|
-
* @private
|
|
2058
|
-
*/
|
|
2059
|
-
notifyJobStateChange(id) {
|
|
2060
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2061
|
-
}
|
|
2062
|
-
/**
|
|
2063
|
-
* Pause a job by ID (sets enabled=false).
|
|
2064
|
-
* Notifies any subscribed state listeners.
|
|
2065
|
-
* @param {string} id - The job ID to pause
|
|
2066
|
-
* @returns {void}
|
|
2067
|
-
*/
|
|
2068
|
-
pauseJob(id) {
|
|
2069
|
-
this.updateJob(id, { enabled: false });
|
|
2070
|
-
this.notifyJobStateChange(id);
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2074
|
-
* Resets job timing to prevent frame accumulation.
|
|
2075
|
-
* Notifies any subscribed state listeners.
|
|
2076
|
-
* @param {string} id - The job ID to resume
|
|
2077
|
-
* @returns {void}
|
|
2078
|
-
*/
|
|
2079
|
-
resumeJob(id) {
|
|
2080
|
-
this.updateJob(id, { enabled: true });
|
|
2081
|
-
this.notifyJobStateChange(id);
|
|
2082
|
-
}
|
|
2083
|
-
//* Frame Loop Control Methods ================================
|
|
2084
|
-
/**
|
|
2085
|
-
* Start the requestAnimationFrame loop.
|
|
2086
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2087
|
-
* No-op if already running.
|
|
2088
|
-
* @returns {void}
|
|
2089
|
-
*/
|
|
2090
|
-
start() {
|
|
2091
|
-
if (this.loopState.running) return;
|
|
2092
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2093
|
-
let adjustedCreated = 0;
|
|
2094
|
-
if (this.stoppedTime > 0) {
|
|
2095
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2096
|
-
this.stoppedTime = 0;
|
|
2097
|
-
}
|
|
2098
|
-
Object.assign(this.loopState, {
|
|
2099
|
-
running: true,
|
|
2100
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2101
|
-
lastTime: performance.now(),
|
|
2102
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2103
|
-
frameCount: 0,
|
|
2104
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2105
|
-
});
|
|
2106
|
-
}
|
|
2107
|
-
/**
|
|
2108
|
-
* Stop the requestAnimationFrame loop.
|
|
2109
|
-
* Cancels any pending RAF callback.
|
|
2110
|
-
* No-op if not running.
|
|
2111
|
-
* @returns {void}
|
|
2112
|
-
*/
|
|
2113
|
-
stop() {
|
|
2114
|
-
if (!this.loopState.running) return;
|
|
2115
|
-
this.loopState.running = false;
|
|
2116
|
-
if (this.loopState.rafHandle !== null) {
|
|
2117
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2118
|
-
this.loopState.rafHandle = null;
|
|
2119
|
-
}
|
|
2120
|
-
this.stoppedTime = performance.now();
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Request frames to be rendered in demand mode.
|
|
2124
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2125
|
-
* No-op if frameloop is not 'demand'.
|
|
2126
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2127
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2128
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2129
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2130
|
-
* @returns {void}
|
|
2131
|
-
* @example
|
|
2132
|
-
* // Request a single frame render
|
|
2133
|
-
* scheduler.invalidate();
|
|
2134
|
-
*
|
|
2135
|
-
* @example
|
|
2136
|
-
* // Request 5 frames (e.g., for animations)
|
|
2137
|
-
* scheduler.invalidate(5);
|
|
2138
|
-
*
|
|
2139
|
-
* @example
|
|
2140
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2141
|
-
* scheduler.invalidate(3, false);
|
|
2142
|
-
*
|
|
2143
|
-
* @example
|
|
2144
|
-
* // Add 2 more frames to existing pending count
|
|
2145
|
-
* scheduler.invalidate(2, true);
|
|
2146
|
-
*/
|
|
2147
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2148
|
-
if (this._frameloop !== "demand") return;
|
|
2149
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2150
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2151
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2152
|
-
}
|
|
2153
|
-
/**
|
|
2154
|
-
* Reset timing state for deterministic testing.
|
|
2155
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2156
|
-
* @returns {void}
|
|
2157
|
-
*/
|
|
2158
|
-
resetTiming() {
|
|
2159
|
-
this.loopState.lastTime = null;
|
|
2160
|
-
this.loopState.frameCount = 0;
|
|
2161
|
-
this.loopState.elapsedTime = 0;
|
|
2162
|
-
this.loopState.createdAt = performance.now();
|
|
2163
|
-
}
|
|
2164
|
-
//* Manual Stepping Methods ================================
|
|
2165
|
-
/**
|
|
2166
|
-
* Manually execute a single frame for all roots.
|
|
2167
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2168
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2169
|
-
* @returns {void}
|
|
2170
|
-
* @example
|
|
2171
|
-
* // Manual control mode
|
|
2172
|
-
* scheduler.frameloop = 'never';
|
|
2173
|
-
* scheduler.step(); // Execute one frame
|
|
2174
|
-
*/
|
|
2175
|
-
step(timestamp) {
|
|
2176
|
-
const now = timestamp ?? performance.now();
|
|
2177
|
-
this.executeFrame(now);
|
|
2178
|
-
}
|
|
2179
|
-
/**
|
|
2180
|
-
* Manually execute a single job by its ID.
|
|
2181
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2182
|
-
* @param {string} id - The job ID to step
|
|
2183
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2184
|
-
* @returns {void}
|
|
2185
|
-
*/
|
|
2186
|
-
stepJob(id, timestamp) {
|
|
2187
|
-
let job;
|
|
2188
|
-
let root;
|
|
2189
|
-
for (const r of this.roots.values()) {
|
|
2190
|
-
job = r.jobs.get(id);
|
|
2191
|
-
if (job) {
|
|
2192
|
-
root = r;
|
|
2193
|
-
break;
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
if (!job || !root) {
|
|
2197
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
const now = timestamp ?? performance.now();
|
|
2201
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2202
|
-
const delta = deltaMs / 1e3;
|
|
2203
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2204
|
-
const providedState = root.getState?.() ?? {};
|
|
2205
|
-
const frameState = {
|
|
2206
|
-
...providedState,
|
|
2207
|
-
time: now,
|
|
2208
|
-
delta,
|
|
2209
|
-
elapsed,
|
|
2210
|
-
frame: this.loopState.frameCount
|
|
2211
|
-
};
|
|
2212
|
-
try {
|
|
2213
|
-
job.callback(frameState, delta);
|
|
2214
|
-
} catch (error) {
|
|
2215
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2216
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
/**
|
|
2220
|
-
* Execute a single frame across all roots.
|
|
2221
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2222
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2223
|
-
* @returns {void}
|
|
2224
|
-
* @private
|
|
2225
|
-
*/
|
|
2226
|
-
executeFrame(timestamp) {
|
|
2227
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2228
|
-
const delta = deltaMs / 1e3;
|
|
2229
|
-
this.loopState.lastTime = timestamp;
|
|
2230
|
-
this.loopState.frameCount++;
|
|
2231
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2232
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2233
|
-
for (const root of this.roots.values()) {
|
|
2234
|
-
this.tickRoot(root, timestamp, delta);
|
|
2235
|
-
}
|
|
2236
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2237
|
-
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Run all global jobs from a job map.
|
|
2240
|
-
* Catches and logs errors without stopping execution.
|
|
2241
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2242
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2243
|
-
* @returns {void}
|
|
2244
|
-
* @private
|
|
2245
|
-
*/
|
|
2246
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2247
|
-
for (const job of jobs.values()) {
|
|
2248
|
-
try {
|
|
2249
|
-
job.callback(timestamp);
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
/**
|
|
2256
|
-
* Execute all jobs for a single root in sorted order.
|
|
2257
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2258
|
-
* Errors are caught and propagated via triggerError.
|
|
2259
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2260
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2261
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2262
|
-
* @returns {void}
|
|
2263
|
-
* @private
|
|
2264
|
-
*/
|
|
2265
|
-
tickRoot(root, timestamp, delta) {
|
|
2266
|
-
if (root.needsRebuild) {
|
|
2267
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2268
|
-
root.needsRebuild = false;
|
|
2269
|
-
}
|
|
2270
|
-
const providedState = root.getState?.() ?? {};
|
|
2271
|
-
const frameState = {
|
|
2272
|
-
...providedState,
|
|
2273
|
-
time: timestamp,
|
|
2274
|
-
delta,
|
|
2275
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2276
|
-
// Convert ms to seconds
|
|
2277
|
-
frame: this.loopState.frameCount
|
|
2278
|
-
};
|
|
2279
|
-
for (const job of root.sortedJobs) {
|
|
2280
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2281
|
-
try {
|
|
2282
|
-
job.callback(frameState, delta);
|
|
2283
|
-
} catch (error) {
|
|
2284
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2285
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
//* Debug & Inspection Methods ================================
|
|
2290
|
-
/**
|
|
2291
|
-
* Get the total number of registered jobs across all roots.
|
|
2292
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2293
|
-
* @returns {number} Total job count
|
|
2294
|
-
*/
|
|
2295
|
-
getJobCount() {
|
|
2296
|
-
let count = 0;
|
|
2297
|
-
for (const root of this.roots.values()) {
|
|
2298
|
-
count += root.jobs.size;
|
|
2299
|
-
}
|
|
2300
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2301
|
-
}
|
|
2302
|
-
/**
|
|
2303
|
-
* Get all registered job IDs across all roots.
|
|
2304
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2305
|
-
* @returns {string[]} Array of all job IDs
|
|
2306
|
-
*/
|
|
2307
|
-
getJobIds() {
|
|
2308
|
-
const ids = [];
|
|
2309
|
-
for (const root of this.roots.values()) {
|
|
2310
|
-
ids.push(...root.jobs.keys());
|
|
2311
|
-
}
|
|
2312
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2313
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2314
|
-
return ids;
|
|
2315
|
-
}
|
|
2316
|
-
/**
|
|
2317
|
-
* Get the number of registered roots (Canvas instances).
|
|
2318
|
-
* @returns {number} Number of registered roots
|
|
2319
|
-
*/
|
|
2320
|
-
getRootCount() {
|
|
2321
|
-
return this.roots.size;
|
|
2322
|
-
}
|
|
2323
|
-
/**
|
|
2324
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2325
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2326
|
-
*
|
|
2327
|
-
* @param phase The phase to check
|
|
2328
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2329
|
-
* @returns true if any user jobs exist in the phase
|
|
2330
|
-
*/
|
|
2331
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2332
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2333
|
-
return rootsToCheck.some((root) => {
|
|
2334
|
-
if (!root) return false;
|
|
2335
|
-
for (const job of root.jobs.values()) {
|
|
2336
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2337
|
-
}
|
|
2338
|
-
return false;
|
|
2339
|
-
});
|
|
2340
|
-
}
|
|
2341
|
-
//* Utility Methods ================================
|
|
2342
|
-
/**
|
|
2343
|
-
* Generate a unique root ID for automatic root registration.
|
|
2344
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2345
|
-
*/
|
|
2346
|
-
generateRootId() {
|
|
2347
|
-
return `root_${this.nextRootIndex++}`;
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Generate a unique job ID.
|
|
2351
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2352
|
-
* @private
|
|
2353
|
-
*/
|
|
2354
|
-
generateJobId() {
|
|
2355
|
-
return `job_${this.nextJobIndex}`;
|
|
2356
|
-
}
|
|
2357
|
-
/**
|
|
2358
|
-
* Normalize before/after constraints to a Set.
|
|
2359
|
-
* Handles undefined, single string, or array inputs.
|
|
2360
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2361
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2362
|
-
* @private
|
|
2363
|
-
*/
|
|
2364
|
-
normalizeConstraints(value) {
|
|
2365
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2366
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2367
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2371
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2372
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2373
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2374
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2375
|
-
let Scheduler = _Scheduler;
|
|
2376
|
-
const getScheduler = () => Scheduler.get();
|
|
2377
|
-
if (hmrData) {
|
|
2378
|
-
hmrData.accept?.();
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
2021
|
function useFrame(callback, priorityOrOptions) {
|
|
2382
2022
|
const store = React__namespace.useContext(context);
|
|
2383
2023
|
const isInsideCanvas = store !== null;
|
|
2384
|
-
const scheduler = getScheduler();
|
|
2024
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
2385
2025
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2386
2026
|
id: priorityOrOptions.id,
|
|
2387
2027
|
phase: priorityOrOptions.phase,
|
|
@@ -2429,7 +2069,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2429
2069
|
};
|
|
2430
2070
|
callbackRef.current?.(mergedState, delta);
|
|
2431
2071
|
};
|
|
2432
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2072
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2433
2073
|
id,
|
|
2434
2074
|
rootId,
|
|
2435
2075
|
...options
|
|
@@ -2450,37 +2090,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2450
2090
|
}
|
|
2451
2091
|
};
|
|
2452
2092
|
} else {
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
unregisterJob = registerOutside();
|
|
2462
|
-
});
|
|
2463
|
-
return () => {
|
|
2464
|
-
unsubReady();
|
|
2465
|
-
unregisterJob?.();
|
|
2466
|
-
};
|
|
2093
|
+
return scheduler$1.register(
|
|
2094
|
+
(state, delta) => {
|
|
2095
|
+
const frameState = state;
|
|
2096
|
+
if (!frameState.renderer) return;
|
|
2097
|
+
callbackRef.current?.(frameState, delta);
|
|
2098
|
+
},
|
|
2099
|
+
{ id, ...options }
|
|
2100
|
+
);
|
|
2467
2101
|
}
|
|
2468
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2102
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2469
2103
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2470
2104
|
// Subscribe function
|
|
2471
2105
|
React__namespace.useCallback(
|
|
2472
2106
|
(onStoreChange) => {
|
|
2473
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2107
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2474
2108
|
},
|
|
2475
2109
|
[id]
|
|
2476
2110
|
),
|
|
2477
2111
|
// getSnapshot function
|
|
2478
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2112
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2479
2113
|
// getServerSnapshot function (SSR)
|
|
2480
2114
|
React__namespace.useCallback(() => false, [])
|
|
2481
2115
|
);
|
|
2482
2116
|
const controls = React__namespace.useMemo(() => {
|
|
2483
|
-
const scheduler2 = getScheduler();
|
|
2117
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2484
2118
|
return {
|
|
2485
2119
|
/** The job's unique ID */
|
|
2486
2120
|
id,
|
|
@@ -2495,7 +2129,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2495
2129
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2496
2130
|
*/
|
|
2497
2131
|
step: (timestamp) => {
|
|
2498
|
-
getScheduler().stepJob(id, timestamp);
|
|
2132
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2499
2133
|
},
|
|
2500
2134
|
/**
|
|
2501
2135
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2503,20 +2137,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2503
2137
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2504
2138
|
*/
|
|
2505
2139
|
stepAll: (timestamp) => {
|
|
2506
|
-
getScheduler().step(timestamp);
|
|
2140
|
+
scheduler.getScheduler().step(timestamp);
|
|
2507
2141
|
},
|
|
2508
2142
|
/**
|
|
2509
2143
|
* Pause this job (set enabled=false).
|
|
2510
2144
|
* Job remains registered but won't run.
|
|
2511
2145
|
*/
|
|
2512
2146
|
pause: () => {
|
|
2513
|
-
getScheduler().pauseJob(id);
|
|
2147
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2514
2148
|
},
|
|
2515
2149
|
/**
|
|
2516
2150
|
* Resume this job (set enabled=true).
|
|
2517
2151
|
*/
|
|
2518
2152
|
resume: () => {
|
|
2519
|
-
getScheduler().resumeJob(id);
|
|
2153
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2520
2154
|
},
|
|
2521
2155
|
/**
|
|
2522
2156
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2554,22 +2188,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2554
2188
|
function useTexture(input, optionsOrOnLoad) {
|
|
2555
2189
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2556
2190
|
const store = useStore();
|
|
2557
|
-
const textureCache = useThree((state) => state.textures);
|
|
2558
2191
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2559
|
-
const { onLoad, cache =
|
|
2192
|
+
const { onLoad, cache = true } = options;
|
|
2193
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2194
|
+
onLoadRef.current = onLoad;
|
|
2195
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2560
2196
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2561
2197
|
const cachedResult = React.useMemo(() => {
|
|
2562
2198
|
if (!cache) return null;
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2199
|
+
const textures = store.getState().textures;
|
|
2200
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2201
|
+
return buildFromCache(input, textures);
|
|
2202
|
+
}, [cache, urls, input, store]);
|
|
2566
2203
|
const loadedTextures = useLoader(
|
|
2567
2204
|
webgpu.TextureLoader,
|
|
2568
2205
|
IsObject(input) ? Object.values(input) : input
|
|
2569
2206
|
);
|
|
2207
|
+
const inputKey = urls.join("\0");
|
|
2570
2208
|
React.useLayoutEffect(() => {
|
|
2571
|
-
if (
|
|
2572
|
-
|
|
2209
|
+
if (cachedResult) return;
|
|
2210
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2211
|
+
onLoadCalledForRef.current = inputKey;
|
|
2212
|
+
onLoadRef.current?.(loadedTextures);
|
|
2213
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2573
2214
|
React.useEffect(() => {
|
|
2574
2215
|
if (cachedResult) return;
|
|
2575
2216
|
if ("initTexture" in renderer) {
|
|
@@ -2602,8 +2243,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2602
2243
|
}, [input, loadedTextures, cachedResult]);
|
|
2603
2244
|
React.useEffect(() => {
|
|
2604
2245
|
if (!cache) return;
|
|
2605
|
-
if (cachedResult) return;
|
|
2606
|
-
const set = store.setState;
|
|
2607
2246
|
const urlTextureMap = [];
|
|
2608
2247
|
if (typeof input === "string") {
|
|
2609
2248
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2617,18 +2256,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2617
2256
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2618
2257
|
}
|
|
2619
2258
|
}
|
|
2620
|
-
|
|
2621
|
-
const
|
|
2622
|
-
let
|
|
2259
|
+
store.setState((state) => {
|
|
2260
|
+
const refs = new Map(state._textureRefs);
|
|
2261
|
+
let textures = state.textures;
|
|
2262
|
+
let added = false;
|
|
2623
2263
|
for (const [url, texture] of urlTextureMap) {
|
|
2624
|
-
if (!
|
|
2625
|
-
|
|
2626
|
-
|
|
2264
|
+
if (!textures.has(url)) {
|
|
2265
|
+
if (!added) {
|
|
2266
|
+
textures = new Map(textures);
|
|
2267
|
+
added = true;
|
|
2268
|
+
}
|
|
2269
|
+
textures.set(url, texture);
|
|
2627
2270
|
}
|
|
2271
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2628
2272
|
}
|
|
2629
|
-
return
|
|
2273
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2274
|
+
});
|
|
2275
|
+
return () => store.setState((state) => {
|
|
2276
|
+
const refs = new Map(state._textureRefs);
|
|
2277
|
+
for (const [url] of urlTextureMap) {
|
|
2278
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2279
|
+
if (next <= 0) refs.delete(url);
|
|
2280
|
+
else refs.set(url, next);
|
|
2281
|
+
}
|
|
2282
|
+
return { _textureRefs: refs };
|
|
2630
2283
|
});
|
|
2631
|
-
}, [cache, input, mappedTextures, store
|
|
2284
|
+
}, [cache, input, mappedTextures, store]);
|
|
2632
2285
|
return mappedTextures;
|
|
2633
2286
|
}
|
|
2634
2287
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2644,108 +2297,92 @@ const Texture = ({
|
|
|
2644
2297
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2645
2298
|
};
|
|
2646
2299
|
|
|
2647
|
-
function
|
|
2648
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
2649
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
2650
|
-
return entry.value;
|
|
2651
|
-
}
|
|
2652
|
-
return null;
|
|
2653
|
-
}
|
|
2654
|
-
function useTextures() {
|
|
2300
|
+
function useTextures(selector) {
|
|
2655
2301
|
const store = useStore();
|
|
2656
|
-
|
|
2657
|
-
const set = store.setState;
|
|
2302
|
+
const registry = React.useMemo(() => {
|
|
2658
2303
|
const getState = store.getState;
|
|
2659
|
-
const
|
|
2660
|
-
|
|
2661
|
-
const newMap = new Map(state.textures);
|
|
2662
|
-
newMap.set(key, value);
|
|
2663
|
-
return { textures: newMap };
|
|
2664
|
-
});
|
|
2665
|
-
};
|
|
2666
|
-
const addMultiple = (items) => {
|
|
2667
|
-
set((state) => {
|
|
2668
|
-
const newMap = new Map(state.textures);
|
|
2669
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2670
|
-
for (const [key, value] of entries) {
|
|
2671
|
-
newMap.set(key, value);
|
|
2672
|
-
}
|
|
2673
|
-
return { textures: newMap };
|
|
2674
|
-
});
|
|
2675
|
-
};
|
|
2676
|
-
const remove = (key) => {
|
|
2677
|
-
set((state) => {
|
|
2678
|
-
const newMap = new Map(state.textures);
|
|
2679
|
-
newMap.delete(key);
|
|
2680
|
-
return { textures: newMap };
|
|
2681
|
-
});
|
|
2682
|
-
};
|
|
2683
|
-
const removeMultiple = (keys) => {
|
|
2684
|
-
set((state) => {
|
|
2685
|
-
const newMap = new Map(state.textures);
|
|
2686
|
-
for (const key of keys) newMap.delete(key);
|
|
2687
|
-
return { textures: newMap };
|
|
2688
|
-
});
|
|
2689
|
-
};
|
|
2690
|
-
const dispose = (key) => {
|
|
2691
|
-
const entry = getState().textures.get(key);
|
|
2692
|
-
if (entry) {
|
|
2693
|
-
const tex = getTextureValue(entry);
|
|
2694
|
-
tex?.dispose();
|
|
2695
|
-
}
|
|
2696
|
-
remove(key);
|
|
2697
|
-
};
|
|
2698
|
-
const disposeMultiple = (keys) => {
|
|
2699
|
-
const textures = getState().textures;
|
|
2700
|
-
for (const key of keys) {
|
|
2701
|
-
const entry = textures.get(key);
|
|
2702
|
-
if (entry) {
|
|
2703
|
-
const tex = getTextureValue(entry);
|
|
2704
|
-
tex?.dispose();
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
removeMultiple(keys);
|
|
2708
|
-
};
|
|
2709
|
-
const disposeAll = () => {
|
|
2710
|
-
const textures = getState().textures;
|
|
2711
|
-
for (const entry of textures.values()) {
|
|
2712
|
-
const tex = getTextureValue(entry);
|
|
2713
|
-
tex?.dispose();
|
|
2714
|
-
}
|
|
2715
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2716
|
-
};
|
|
2304
|
+
const setState = store.setState;
|
|
2305
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2717
2306
|
return {
|
|
2718
|
-
|
|
2719
|
-
get textures() {
|
|
2307
|
+
get all() {
|
|
2720
2308
|
return getState().textures;
|
|
2721
2309
|
},
|
|
2722
|
-
|
|
2723
|
-
|
|
2310
|
+
get(input) {
|
|
2311
|
+
if (typeof input === "string") return getOne(input);
|
|
2312
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2313
|
+
const out = {};
|
|
2314
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2315
|
+
return out;
|
|
2316
|
+
},
|
|
2724
2317
|
has: (key) => getState().textures.has(key),
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2318
|
+
add(keyOrRecord, texture) {
|
|
2319
|
+
setState((state) => {
|
|
2320
|
+
const textures = new Map(state.textures);
|
|
2321
|
+
if (typeof keyOrRecord === "string") {
|
|
2322
|
+
textures.set(keyOrRecord, texture);
|
|
2323
|
+
} else {
|
|
2324
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2325
|
+
}
|
|
2326
|
+
return { textures };
|
|
2327
|
+
});
|
|
2328
|
+
},
|
|
2329
|
+
dispose(key, options) {
|
|
2330
|
+
const state = getState();
|
|
2331
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2332
|
+
if (refs > 0 && !options?.force) {
|
|
2333
|
+
console.warn(
|
|
2334
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2335
|
+
);
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
state.textures.get(key)?.dispose();
|
|
2339
|
+
setState((s) => {
|
|
2340
|
+
const textures = new Map(s.textures);
|
|
2341
|
+
textures.delete(key);
|
|
2342
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2343
|
+
nextRefs.delete(key);
|
|
2344
|
+
return { textures, _textureRefs: nextRefs };
|
|
2345
|
+
});
|
|
2346
|
+
return true;
|
|
2347
|
+
},
|
|
2348
|
+
disposeAll() {
|
|
2349
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2350
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2351
|
+
}
|
|
2735
2352
|
};
|
|
2736
2353
|
}, [store]);
|
|
2354
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2355
|
+
const selected = useThree(subscribe);
|
|
2356
|
+
return selector ? selected : registry;
|
|
2737
2357
|
}
|
|
2738
2358
|
|
|
2739
|
-
function useRenderTarget(
|
|
2359
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2740
2360
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2741
2361
|
const size = useThree((s) => s.size);
|
|
2362
|
+
let width;
|
|
2363
|
+
let height;
|
|
2364
|
+
let opts;
|
|
2365
|
+
if (typeof widthOrOptions === "object") {
|
|
2366
|
+
opts = widthOrOptions;
|
|
2367
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2368
|
+
width = widthOrOptions;
|
|
2369
|
+
if (typeof heightOrOptions === "object") {
|
|
2370
|
+
height = widthOrOptions;
|
|
2371
|
+
opts = heightOrOptions;
|
|
2372
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2373
|
+
height = heightOrOptions;
|
|
2374
|
+
opts = options;
|
|
2375
|
+
} else {
|
|
2376
|
+
height = widthOrOptions;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2742
2379
|
return React.useMemo(() => {
|
|
2743
2380
|
const w = width ?? size.width;
|
|
2744
2381
|
const h = height ?? size.height;
|
|
2745
2382
|
{
|
|
2746
|
-
return isLegacy ? new three.WebGLRenderTarget(w, h,
|
|
2383
|
+
return isLegacy ? new three.WebGLRenderTarget(w, h, opts) : new webgpu.RenderTarget(w, h, opts);
|
|
2747
2384
|
}
|
|
2748
|
-
}, [width, height, size.width, size.height,
|
|
2385
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2749
2386
|
}
|
|
2750
2387
|
|
|
2751
2388
|
function useStore() {
|
|
@@ -2773,7 +2410,7 @@ function addEffect(callback) {
|
|
|
2773
2410
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2774
2411
|
});
|
|
2775
2412
|
const id = `legacy_effect_${effectId++}`;
|
|
2776
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2413
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2777
2414
|
}
|
|
2778
2415
|
function addAfterEffect(callback) {
|
|
2779
2416
|
notifyDepreciated({
|
|
@@ -2782,7 +2419,7 @@ function addAfterEffect(callback) {
|
|
|
2782
2419
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2783
2420
|
});
|
|
2784
2421
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2785
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2422
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2786
2423
|
}
|
|
2787
2424
|
function addTail(callback) {
|
|
2788
2425
|
notifyDepreciated({
|
|
@@ -2790,16 +2427,16 @@ function addTail(callback) {
|
|
|
2790
2427
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2791
2428
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2792
2429
|
});
|
|
2793
|
-
return getScheduler().onIdle(callback);
|
|
2430
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2794
2431
|
}
|
|
2795
2432
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2796
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2433
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2797
2434
|
}
|
|
2798
|
-
function advance(timestamp
|
|
2799
|
-
getScheduler().step(timestamp);
|
|
2435
|
+
function advance(timestamp) {
|
|
2436
|
+
scheduler.getScheduler().step(timestamp);
|
|
2800
2437
|
}
|
|
2801
2438
|
|
|
2802
|
-
const version = "10.0.0-alpha.
|
|
2439
|
+
const version = "10.0.0-alpha.3";
|
|
2803
2440
|
const packageData = {
|
|
2804
2441
|
version: version};
|
|
2805
2442
|
|
|
@@ -14238,6 +13875,7 @@ function swapInstances() {
|
|
|
14238
13875
|
}
|
|
14239
13876
|
}
|
|
14240
13877
|
if (instance.isHidden) unhideInstance(instance);
|
|
13878
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14241
13879
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14242
13880
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14243
13881
|
}
|
|
@@ -14249,6 +13887,9 @@ function swapInstances() {
|
|
|
14249
13887
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14250
13888
|
instance.object.__r3f = instance;
|
|
14251
13889
|
setFiberRef(fiber, instance.object);
|
|
13890
|
+
delete instance.appliedOnce;
|
|
13891
|
+
instance.eventCount = 0;
|
|
13892
|
+
instance.handlers = {};
|
|
14252
13893
|
applyProps(instance.object, instance.props);
|
|
14253
13894
|
if (instance.props.attach) {
|
|
14254
13895
|
attach(parent, instance);
|
|
@@ -14322,8 +13963,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14322
13963
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14323
13964
|
if (isTailSibling) swapInstances();
|
|
14324
13965
|
},
|
|
14325
|
-
finalizeInitialChildren: () =>
|
|
14326
|
-
|
|
13966
|
+
finalizeInitialChildren: (instance) => {
|
|
13967
|
+
for (const prop in instance.props) {
|
|
13968
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13969
|
+
}
|
|
13970
|
+
return false;
|
|
13971
|
+
},
|
|
13972
|
+
commitMount(instance) {
|
|
13973
|
+
const resolved = {};
|
|
13974
|
+
for (const prop in instance.props) {
|
|
13975
|
+
const value = instance.props[prop];
|
|
13976
|
+
if (isFromRef(value)) {
|
|
13977
|
+
const ref = value[FROM_REF];
|
|
13978
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13979
|
+
}
|
|
13980
|
+
}
|
|
13981
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14327
13982
|
},
|
|
14328
13983
|
getPublicInstance: (instance) => instance?.object,
|
|
14329
13984
|
prepareForCommit: () => null,
|
|
@@ -14450,11 +14105,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14450
14105
|
cloneMutableTextInstance(textInstance) {
|
|
14451
14106
|
return textInstance;
|
|
14452
14107
|
},
|
|
14108
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14109
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14110
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14111
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14112
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14113
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14453
14114
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14454
|
-
throw new Error("Not implemented.");
|
|
14455
14115
|
},
|
|
14456
14116
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14457
|
-
throw new Error("Not implemented.");
|
|
14458
14117
|
},
|
|
14459
14118
|
// https://github.com/facebook/react/pull/32465
|
|
14460
14119
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14539,11 +14198,15 @@ function createRoot(canvas) {
|
|
|
14539
14198
|
const lastConfiguredProps = {};
|
|
14540
14199
|
let configured = false;
|
|
14541
14200
|
let pending = null;
|
|
14201
|
+
let rendererSetup = null;
|
|
14542
14202
|
return {
|
|
14543
14203
|
async configure(props = {}) {
|
|
14544
14204
|
let resolve;
|
|
14545
14205
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14546
14206
|
const {
|
|
14207
|
+
id: canvasId,
|
|
14208
|
+
primaryCanvas,
|
|
14209
|
+
scheduler: schedulerConfig,
|
|
14547
14210
|
gl: glConfig,
|
|
14548
14211
|
renderer: rendererConfig,
|
|
14549
14212
|
size: propsSize,
|
|
@@ -14551,10 +14214,6 @@ function createRoot(canvas) {
|
|
|
14551
14214
|
events,
|
|
14552
14215
|
onCreated: onCreatedCallback,
|
|
14553
14216
|
shadows = false,
|
|
14554
|
-
linear = false,
|
|
14555
|
-
flat = false,
|
|
14556
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14557
|
-
legacy = false,
|
|
14558
14217
|
orthographic = false,
|
|
14559
14218
|
frameloop = "always",
|
|
14560
14219
|
dpr = [1, 2],
|
|
@@ -14566,9 +14225,11 @@ function createRoot(canvas) {
|
|
|
14566
14225
|
onDropMissed,
|
|
14567
14226
|
autoUpdateFrustum = true,
|
|
14568
14227
|
occlusion = false,
|
|
14569
|
-
_sizeProps
|
|
14228
|
+
_sizeProps,
|
|
14229
|
+
forceEven
|
|
14570
14230
|
} = props;
|
|
14571
|
-
const
|
|
14231
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14232
|
+
let state = store.getState();
|
|
14572
14233
|
const defaultGLProps = {
|
|
14573
14234
|
canvas,
|
|
14574
14235
|
powerPreference: "high-performance",
|
|
@@ -14576,7 +14237,8 @@ function createRoot(canvas) {
|
|
|
14576
14237
|
alpha: true
|
|
14577
14238
|
};
|
|
14578
14239
|
const defaultGPUProps = {
|
|
14579
|
-
canvas
|
|
14240
|
+
canvas,
|
|
14241
|
+
antialias: true
|
|
14580
14242
|
};
|
|
14581
14243
|
const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU || !rendererConfig);
|
|
14582
14244
|
if (glConfig && rendererConfig) {
|
|
@@ -14590,19 +14252,73 @@ function createRoot(canvas) {
|
|
|
14590
14252
|
});
|
|
14591
14253
|
}
|
|
14592
14254
|
let renderer = state.internal.actualRenderer;
|
|
14593
|
-
if (
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
}
|
|
14598
|
-
|
|
14599
|
-
if (!
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14255
|
+
if (primaryCanvas && wantsGL) {
|
|
14256
|
+
throw new Error(
|
|
14257
|
+
"The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
|
|
14258
|
+
);
|
|
14259
|
+
}
|
|
14260
|
+
if (!state.internal.actualRenderer) {
|
|
14261
|
+
if (!rendererSetup) {
|
|
14262
|
+
rendererSetup = (async () => {
|
|
14263
|
+
if (wantsGL) {
|
|
14264
|
+
renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
|
|
14265
|
+
state.internal.actualRenderer = renderer;
|
|
14266
|
+
state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
|
|
14267
|
+
} else if (!wantsGL && primaryCanvas) {
|
|
14268
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14269
|
+
renderer = primary.renderer;
|
|
14270
|
+
state.internal.actualRenderer = renderer;
|
|
14271
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14272
|
+
primary.store.setState((prev) => ({
|
|
14273
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14274
|
+
}));
|
|
14275
|
+
state.set((prev) => ({
|
|
14276
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14277
|
+
renderer,
|
|
14278
|
+
primaryStore: primary.store,
|
|
14279
|
+
internal: {
|
|
14280
|
+
...prev.internal,
|
|
14281
|
+
canvasTarget,
|
|
14282
|
+
isMultiCanvas: true,
|
|
14283
|
+
isSecondary: true,
|
|
14284
|
+
targetId: primaryCanvas
|
|
14285
|
+
}
|
|
14286
|
+
}));
|
|
14287
|
+
} else if (!wantsGL) {
|
|
14288
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14289
|
+
if (!renderer.hasInitialized?.()) {
|
|
14290
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14291
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14292
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14293
|
+
canvas.width = size2.width * pixelRatio;
|
|
14294
|
+
canvas.height = size2.height * pixelRatio;
|
|
14295
|
+
}
|
|
14296
|
+
await renderer.init();
|
|
14297
|
+
}
|
|
14298
|
+
const backend = renderer.backend;
|
|
14299
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14300
|
+
state.internal.actualRenderer = renderer;
|
|
14301
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14302
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14303
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14304
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14305
|
+
state.set((prev) => ({
|
|
14306
|
+
internal: {
|
|
14307
|
+
...prev.internal,
|
|
14308
|
+
canvasTarget,
|
|
14309
|
+
unregisterPrimary
|
|
14310
|
+
}
|
|
14311
|
+
}));
|
|
14312
|
+
}
|
|
14313
|
+
}
|
|
14314
|
+
})().catch((err) => {
|
|
14315
|
+
rendererSetup = null;
|
|
14316
|
+
throw err;
|
|
14317
|
+
});
|
|
14318
|
+
}
|
|
14319
|
+
await rendererSetup;
|
|
14320
|
+
state = store.getState();
|
|
14321
|
+
renderer = state.internal.actualRenderer;
|
|
14606
14322
|
}
|
|
14607
14323
|
let raycaster = state.raycaster;
|
|
14608
14324
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -14611,6 +14327,7 @@ function createRoot(canvas) {
|
|
|
14611
14327
|
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14612
14328
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14613
14329
|
}
|
|
14330
|
+
let tempCamera = state.camera;
|
|
14614
14331
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14615
14332
|
lastCamera = cameraOptions;
|
|
14616
14333
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14630,6 +14347,7 @@ function createRoot(canvas) {
|
|
|
14630
14347
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14631
14348
|
}
|
|
14632
14349
|
state.set({ camera });
|
|
14350
|
+
tempCamera = camera;
|
|
14633
14351
|
raycaster.camera = camera;
|
|
14634
14352
|
}
|
|
14635
14353
|
if (!state.scene) {
|
|
@@ -14647,7 +14365,7 @@ function createRoot(canvas) {
|
|
|
14647
14365
|
rootScene: scene,
|
|
14648
14366
|
internal: { ...prev.internal, container: scene }
|
|
14649
14367
|
}));
|
|
14650
|
-
const camera =
|
|
14368
|
+
const camera = tempCamera;
|
|
14651
14369
|
if (camera && !camera.parent) scene.add(camera);
|
|
14652
14370
|
}
|
|
14653
14371
|
if (events && !state.events.handlers) {
|
|
@@ -14664,6 +14382,9 @@ function createRoot(canvas) {
|
|
|
14664
14382
|
if (_sizeProps !== void 0) {
|
|
14665
14383
|
state.set({ _sizeProps });
|
|
14666
14384
|
}
|
|
14385
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14386
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14387
|
+
}
|
|
14667
14388
|
const size = computeInitialSize(canvas, propsSize);
|
|
14668
14389
|
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14669
14390
|
const wasImperative = state._sizeImperative;
|
|
@@ -14690,10 +14411,10 @@ function createRoot(canvas) {
|
|
|
14690
14411
|
lastConfiguredProps.performance = performance;
|
|
14691
14412
|
}
|
|
14692
14413
|
if (!state.xr) {
|
|
14693
|
-
const handleXRFrame = (timestamp,
|
|
14414
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14694
14415
|
const state2 = store.getState();
|
|
14695
14416
|
if (state2.frameloop === "never") return;
|
|
14696
|
-
advance(timestamp
|
|
14417
|
+
advance(timestamp);
|
|
14697
14418
|
};
|
|
14698
14419
|
const actualRenderer = state.internal.actualRenderer;
|
|
14699
14420
|
const handleSessionChange = () => {
|
|
@@ -14705,16 +14426,16 @@ function createRoot(canvas) {
|
|
|
14705
14426
|
};
|
|
14706
14427
|
const xr = {
|
|
14707
14428
|
connect() {
|
|
14708
|
-
const { gl, renderer: renderer2
|
|
14709
|
-
const
|
|
14710
|
-
|
|
14711
|
-
|
|
14429
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14430
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14431
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14432
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14712
14433
|
},
|
|
14713
14434
|
disconnect() {
|
|
14714
|
-
const { gl, renderer: renderer2
|
|
14715
|
-
const
|
|
14716
|
-
|
|
14717
|
-
|
|
14435
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14436
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14437
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14438
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14718
14439
|
}
|
|
14719
14440
|
};
|
|
14720
14441
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14726,15 +14447,22 @@ function createRoot(canvas) {
|
|
|
14726
14447
|
const oldType = renderer.shadowMap.type;
|
|
14727
14448
|
renderer.shadowMap.enabled = !!shadows;
|
|
14728
14449
|
if (is.boo(shadows)) {
|
|
14729
|
-
renderer.shadowMap.type = webgpu.
|
|
14450
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14730
14451
|
} else if (is.str(shadows)) {
|
|
14452
|
+
if (shadows === "soft") {
|
|
14453
|
+
notifyDepreciated({
|
|
14454
|
+
heading: 'shadows="soft" is deprecated',
|
|
14455
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14456
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14457
|
+
});
|
|
14458
|
+
}
|
|
14731
14459
|
const types = {
|
|
14732
14460
|
basic: webgpu.BasicShadowMap,
|
|
14733
14461
|
percentage: webgpu.PCFShadowMap,
|
|
14734
|
-
soft: webgpu.
|
|
14462
|
+
soft: webgpu.PCFShadowMap,
|
|
14735
14463
|
variance: webgpu.VSMShadowMap
|
|
14736
14464
|
};
|
|
14737
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14465
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14738
14466
|
} else if (is.obj(shadows)) {
|
|
14739
14467
|
Object.assign(renderer.shadowMap, shadows);
|
|
14740
14468
|
}
|
|
@@ -14742,57 +14470,70 @@ function createRoot(canvas) {
|
|
|
14742
14470
|
renderer.shadowMap.needsUpdate = true;
|
|
14743
14471
|
}
|
|
14744
14472
|
}
|
|
14745
|
-
{
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
const flatChanged = flat !== lastConfiguredProps.flat;
|
|
14749
|
-
if (legacyChanged) {
|
|
14750
|
-
if (legacy) {
|
|
14751
|
-
notifyDepreciated({
|
|
14752
|
-
heading: "Legacy Color Management",
|
|
14753
|
-
body: "Legacy color management is deprecated and will be removed in a future version.",
|
|
14754
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
14755
|
-
});
|
|
14756
|
-
}
|
|
14757
|
-
}
|
|
14758
|
-
if (legacyChanged) {
|
|
14759
|
-
webgpu.ColorManagement.enabled = !legacy;
|
|
14760
|
-
lastConfiguredProps.legacy = legacy;
|
|
14761
|
-
}
|
|
14762
|
-
if (!configured || linearChanged) {
|
|
14763
|
-
renderer.outputColorSpace = linear ? webgpu.LinearSRGBColorSpace : webgpu.SRGBColorSpace;
|
|
14764
|
-
lastConfiguredProps.linear = linear;
|
|
14765
|
-
}
|
|
14766
|
-
if (!configured || flatChanged) {
|
|
14767
|
-
renderer.toneMapping = flat ? webgpu.NoToneMapping : webgpu.ACESFilmicToneMapping;
|
|
14768
|
-
lastConfiguredProps.flat = flat;
|
|
14769
|
-
}
|
|
14770
|
-
if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
|
|
14771
|
-
if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
|
|
14772
|
-
if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
|
|
14473
|
+
if (!configured) {
|
|
14474
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14475
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14773
14476
|
}
|
|
14774
14477
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14775
14478
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14776
14479
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14777
14480
|
}
|
|
14481
|
+
const r3fProps = ["textureColorSpace"];
|
|
14482
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14483
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14778
14484
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14779
|
-
|
|
14485
|
+
const glProps = {};
|
|
14486
|
+
for (const key in glConfig) {
|
|
14487
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14488
|
+
}
|
|
14489
|
+
applyProps(renderer, glProps);
|
|
14780
14490
|
}
|
|
14781
14491
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14782
14492
|
const currentRenderer = state.renderer;
|
|
14783
14493
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14784
|
-
|
|
14494
|
+
const rendererProps = {};
|
|
14495
|
+
for (const key in rendererConfig) {
|
|
14496
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14497
|
+
}
|
|
14498
|
+
applyProps(currentRenderer, rendererProps);
|
|
14785
14499
|
}
|
|
14786
14500
|
}
|
|
14787
|
-
const scheduler = getScheduler();
|
|
14501
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14788
14502
|
const rootId = state.internal.rootId;
|
|
14789
14503
|
if (!rootId) {
|
|
14790
|
-
const newRootId = scheduler.generateRootId();
|
|
14791
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14504
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14505
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14792
14506
|
getState: () => store.getState(),
|
|
14793
14507
|
onError: (err) => store.getState().setError(err)
|
|
14794
14508
|
});
|
|
14795
|
-
const
|
|
14509
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14510
|
+
() => {
|
|
14511
|
+
const state2 = store.getState();
|
|
14512
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14513
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14514
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14515
|
+
}
|
|
14516
|
+
},
|
|
14517
|
+
{
|
|
14518
|
+
id: `${newRootId}_canvasTarget`,
|
|
14519
|
+
rootId: newRootId,
|
|
14520
|
+
phase: "start",
|
|
14521
|
+
system: true
|
|
14522
|
+
}
|
|
14523
|
+
);
|
|
14524
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14525
|
+
() => {
|
|
14526
|
+
const state2 = store.getState();
|
|
14527
|
+
state2.events.flush?.();
|
|
14528
|
+
},
|
|
14529
|
+
{
|
|
14530
|
+
id: `${newRootId}_events`,
|
|
14531
|
+
rootId: newRootId,
|
|
14532
|
+
phase: "input",
|
|
14533
|
+
system: true
|
|
14534
|
+
}
|
|
14535
|
+
);
|
|
14536
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14796
14537
|
() => {
|
|
14797
14538
|
const state2 = store.getState();
|
|
14798
14539
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14802,11 +14543,11 @@ function createRoot(canvas) {
|
|
|
14802
14543
|
{
|
|
14803
14544
|
id: `${newRootId}_frustum`,
|
|
14804
14545
|
rootId: newRootId,
|
|
14805
|
-
|
|
14546
|
+
before: "render",
|
|
14806
14547
|
system: true
|
|
14807
14548
|
}
|
|
14808
14549
|
);
|
|
14809
|
-
const unregisterVisibility = scheduler.register(
|
|
14550
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14810
14551
|
() => {
|
|
14811
14552
|
const state2 = store.getState();
|
|
14812
14553
|
checkVisibility(state2);
|
|
@@ -14814,30 +14555,34 @@ function createRoot(canvas) {
|
|
|
14814
14555
|
{
|
|
14815
14556
|
id: `${newRootId}_visibility`,
|
|
14816
14557
|
rootId: newRootId,
|
|
14817
|
-
|
|
14558
|
+
before: "render",
|
|
14818
14559
|
system: true,
|
|
14819
14560
|
after: `${newRootId}_frustum`
|
|
14820
14561
|
}
|
|
14821
14562
|
);
|
|
14822
|
-
const unregisterRender = scheduler.register(
|
|
14563
|
+
const unregisterRender = scheduler$1.register(
|
|
14823
14564
|
() => {
|
|
14824
14565
|
const state2 = store.getState();
|
|
14825
14566
|
const renderer2 = state2.internal.actualRenderer;
|
|
14826
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14567
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14827
14568
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14828
14569
|
try {
|
|
14829
|
-
if (state2.
|
|
14570
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14830
14571
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14831
14572
|
} catch (error) {
|
|
14832
14573
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14833
14574
|
}
|
|
14834
14575
|
},
|
|
14835
14576
|
{
|
|
14836
|
-
|
|
14577
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14578
|
+
id: canvasId || `${newRootId}_render`,
|
|
14837
14579
|
rootId: newRootId,
|
|
14838
14580
|
phase: "render",
|
|
14839
|
-
system: true
|
|
14581
|
+
system: true,
|
|
14840
14582
|
// Internal flag: this is a system job, not user-controlled
|
|
14583
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14584
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14585
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14841
14586
|
}
|
|
14842
14587
|
);
|
|
14843
14588
|
state.set((state2) => ({
|
|
@@ -14846,15 +14591,17 @@ function createRoot(canvas) {
|
|
|
14846
14591
|
rootId: newRootId,
|
|
14847
14592
|
unregisterRoot: () => {
|
|
14848
14593
|
unregisterRoot();
|
|
14594
|
+
unregisterCanvasTarget();
|
|
14595
|
+
unregisterEventsFlush();
|
|
14849
14596
|
unregisterFrustum();
|
|
14850
14597
|
unregisterVisibility();
|
|
14851
14598
|
unregisterRender();
|
|
14852
14599
|
},
|
|
14853
|
-
scheduler
|
|
14600
|
+
scheduler: scheduler$1
|
|
14854
14601
|
}
|
|
14855
14602
|
}));
|
|
14856
14603
|
}
|
|
14857
|
-
scheduler.frameloop = frameloop;
|
|
14604
|
+
scheduler$1.frameloop = frameloop;
|
|
14858
14605
|
onCreated = onCreatedCallback;
|
|
14859
14606
|
configured = true;
|
|
14860
14607
|
resolve();
|
|
@@ -14904,15 +14651,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14904
14651
|
const renderer = state.internal.actualRenderer;
|
|
14905
14652
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14906
14653
|
if (unregisterRoot) unregisterRoot();
|
|
14654
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14655
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14656
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14657
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14907
14658
|
state.events.disconnect?.();
|
|
14908
14659
|
cleanupHelperGroup(root.store);
|
|
14909
|
-
renderer
|
|
14910
|
-
|
|
14911
|
-
|
|
14660
|
+
if (state.isLegacy && renderer) {
|
|
14661
|
+
;
|
|
14662
|
+
renderer.renderLists?.dispose?.();
|
|
14663
|
+
renderer.forceContextLoss?.();
|
|
14664
|
+
}
|
|
14665
|
+
if (!state.internal.isSecondary) {
|
|
14666
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14667
|
+
}
|
|
14912
14668
|
dispose(state.scene);
|
|
14913
14669
|
_roots.delete(canvas);
|
|
14914
14670
|
if (callback) callback(canvas);
|
|
14915
|
-
} catch
|
|
14671
|
+
} catch {
|
|
14916
14672
|
}
|
|
14917
14673
|
}, 500);
|
|
14918
14674
|
}
|
|
@@ -14920,36 +14676,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14920
14676
|
}
|
|
14921
14677
|
}
|
|
14922
14678
|
function createPortal(children, container, state) {
|
|
14923
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14679
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14924
14680
|
}
|
|
14925
|
-
function
|
|
14681
|
+
function Portal({ children, container, state }) {
|
|
14926
14682
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14927
|
-
const [resolvedContainer,
|
|
14683
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14928
14684
|
if (isRef(container)) return container.current ?? null;
|
|
14929
14685
|
return container;
|
|
14930
14686
|
});
|
|
14687
|
+
const setResolvedContainer = React.useCallback(
|
|
14688
|
+
(newContainer) => {
|
|
14689
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14690
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14691
|
+
},
|
|
14692
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14693
|
+
);
|
|
14931
14694
|
React.useMemo(() => {
|
|
14932
|
-
if (isRef(container)) {
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
const updated = container.current;
|
|
14937
|
-
if (updated && updated !== resolvedContainer) {
|
|
14938
|
-
setResolvedContainer(updated);
|
|
14939
|
-
}
|
|
14940
|
-
});
|
|
14941
|
-
} else if (current !== resolvedContainer) {
|
|
14942
|
-
setResolvedContainer(current);
|
|
14943
|
-
}
|
|
14944
|
-
} else if (container !== resolvedContainer) {
|
|
14945
|
-
setResolvedContainer(container);
|
|
14695
|
+
if (isRef(container) && !container.current) {
|
|
14696
|
+
return queueMicrotask(() => {
|
|
14697
|
+
setResolvedContainer(container.current);
|
|
14698
|
+
});
|
|
14946
14699
|
}
|
|
14947
|
-
|
|
14700
|
+
setResolvedContainer(container);
|
|
14701
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14948
14702
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14949
14703
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14950
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14704
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14951
14705
|
}
|
|
14952
|
-
function
|
|
14706
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14953
14707
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14954
14708
|
const previousRoot = useStore();
|
|
14955
14709
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14970,12 +14724,14 @@ function Portal({ state = {}, children, container }) {
|
|
|
14970
14724
|
};
|
|
14971
14725
|
}, [portalScene, container, injectScene]);
|
|
14972
14726
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14727
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14973
14728
|
let viewport = void 0;
|
|
14974
|
-
if (injectState.camera && size) {
|
|
14729
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14975
14730
|
const camera = injectState.camera;
|
|
14976
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14977
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14731
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14732
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14978
14733
|
}
|
|
14734
|
+
const set = injectState.set;
|
|
14979
14735
|
return {
|
|
14980
14736
|
// The intersect consists of the previous root state
|
|
14981
14737
|
...rootState,
|
|
@@ -14991,10 +14747,10 @@ function Portal({ state = {}, children, container }) {
|
|
|
14991
14747
|
previousRoot,
|
|
14992
14748
|
// Events, size and viewport can be overridden by the inject layer
|
|
14993
14749
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14994
|
-
size:
|
|
14750
|
+
size: resolvedSize,
|
|
14995
14751
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14996
14752
|
// Layers are allowed to override events
|
|
14997
|
-
setEvents: (events2) =>
|
|
14753
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
14998
14754
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
14999
14755
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15000
14756
|
};
|
|
@@ -15003,9 +14759,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
15003
14759
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15004
14760
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15005
14761
|
onMutate(previousRoot.getState());
|
|
15006
|
-
previousRoot.subscribe(onMutate);
|
|
15007
14762
|
return store;
|
|
15008
14763
|
}, [previousRoot, container]);
|
|
14764
|
+
useIsomorphicLayoutEffect(() => {
|
|
14765
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14766
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14767
|
+
return unsubscribe;
|
|
14768
|
+
}, [previousRoot, usePortalStore]);
|
|
15009
14769
|
return (
|
|
15010
14770
|
// @ts-ignore, reconciler types are not maintained
|
|
15011
14771
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -15019,21 +14779,72 @@ function flushSync(fn) {
|
|
|
15019
14779
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15020
14780
|
}
|
|
15021
14781
|
|
|
14782
|
+
function parseBackground(background) {
|
|
14783
|
+
if (!background) return null;
|
|
14784
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14785
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14786
|
+
return {
|
|
14787
|
+
...rest,
|
|
14788
|
+
preset,
|
|
14789
|
+
files: envMap || files,
|
|
14790
|
+
backgroundFiles: backgroundMap,
|
|
14791
|
+
background: true
|
|
14792
|
+
};
|
|
14793
|
+
}
|
|
14794
|
+
if (typeof background === "number") {
|
|
14795
|
+
return { color: background, background: true };
|
|
14796
|
+
}
|
|
14797
|
+
if (typeof background === "string") {
|
|
14798
|
+
if (background in presetsObj) {
|
|
14799
|
+
return { preset: background, background: true };
|
|
14800
|
+
}
|
|
14801
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14802
|
+
return { files: background, background: true };
|
|
14803
|
+
}
|
|
14804
|
+
return { color: background, background: true };
|
|
14805
|
+
}
|
|
14806
|
+
if (background.isColor) {
|
|
14807
|
+
return { color: background, background: true };
|
|
14808
|
+
}
|
|
14809
|
+
return null;
|
|
14810
|
+
}
|
|
14811
|
+
|
|
14812
|
+
function parseRendererConfig(rendererProp) {
|
|
14813
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14814
|
+
if (isRendererConfig) {
|
|
14815
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14816
|
+
return {
|
|
14817
|
+
primaryCanvas: pc,
|
|
14818
|
+
scheduler: sc,
|
|
14819
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14820
|
+
};
|
|
14821
|
+
}
|
|
14822
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14823
|
+
}
|
|
14824
|
+
|
|
14825
|
+
function clearHmrCaches(store) {
|
|
14826
|
+
store.setState((state) => ({
|
|
14827
|
+
nodes: {},
|
|
14828
|
+
uniforms: {},
|
|
14829
|
+
buffers: {},
|
|
14830
|
+
gpuStorage: {},
|
|
14831
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14832
|
+
}));
|
|
14833
|
+
}
|
|
14834
|
+
|
|
15022
14835
|
function CanvasImpl({
|
|
15023
14836
|
ref,
|
|
15024
14837
|
children,
|
|
15025
14838
|
fallback,
|
|
15026
14839
|
resize,
|
|
15027
14840
|
style,
|
|
14841
|
+
id,
|
|
15028
14842
|
gl,
|
|
15029
|
-
renderer,
|
|
14843
|
+
renderer: rendererProp,
|
|
15030
14844
|
events = createPointerEvents,
|
|
15031
14845
|
eventSource,
|
|
15032
14846
|
eventPrefix,
|
|
15033
14847
|
shadows,
|
|
15034
|
-
linear,
|
|
15035
|
-
flat,
|
|
15036
|
-
legacy,
|
|
15037
14848
|
orthographic,
|
|
15038
14849
|
frameloop,
|
|
15039
14850
|
dpr,
|
|
@@ -15041,6 +14852,8 @@ function CanvasImpl({
|
|
|
15041
14852
|
raycaster,
|
|
15042
14853
|
camera,
|
|
15043
14854
|
scene,
|
|
14855
|
+
autoUpdateFrustum,
|
|
14856
|
+
occlusion,
|
|
15044
14857
|
onPointerMissed,
|
|
15045
14858
|
onDragOverMissed,
|
|
15046
14859
|
onDropMissed,
|
|
@@ -15048,10 +14861,14 @@ function CanvasImpl({
|
|
|
15048
14861
|
hmr,
|
|
15049
14862
|
width,
|
|
15050
14863
|
height,
|
|
14864
|
+
background,
|
|
14865
|
+
forceEven,
|
|
15051
14866
|
...props
|
|
15052
14867
|
}) {
|
|
14868
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15053
14869
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15054
14870
|
const Bridge = useBridge();
|
|
14871
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15055
14872
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15056
14873
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15057
14874
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15068,15 +14885,20 @@ function CanvasImpl({
|
|
|
15068
14885
|
};
|
|
15069
14886
|
}, [resize, hasInitialSizeRef.current]);
|
|
15070
14887
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15071
|
-
const effectiveSize = React__namespace.useMemo(
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
14888
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14889
|
+
let w = width ?? containerRect.width;
|
|
14890
|
+
let h = height ?? containerRect.height;
|
|
14891
|
+
if (forceEven) {
|
|
14892
|
+
w = Math.ceil(w / 2) * 2;
|
|
14893
|
+
h = Math.ceil(h / 2) * 2;
|
|
14894
|
+
}
|
|
14895
|
+
return {
|
|
14896
|
+
width: w,
|
|
14897
|
+
height: h,
|
|
15075
14898
|
top: containerRect.top,
|
|
15076
14899
|
left: containerRect.left
|
|
15077
|
-
}
|
|
15078
|
-
|
|
15079
|
-
);
|
|
14900
|
+
};
|
|
14901
|
+
}, [width, height, containerRect, forceEven]);
|
|
15080
14902
|
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15081
14903
|
hasInitialSizeRef.current = true;
|
|
15082
14904
|
}
|
|
@@ -15088,6 +14910,7 @@ function CanvasImpl({
|
|
|
15088
14910
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15089
14911
|
const [block, setBlock] = React__namespace.useState(false);
|
|
15090
14912
|
const [error, setError] = React__namespace.useState(false);
|
|
14913
|
+
const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
|
|
15091
14914
|
if (block) throw block;
|
|
15092
14915
|
if (error) throw error;
|
|
15093
14916
|
const root = React__namespace.useRef(null);
|
|
@@ -15095,6 +14918,7 @@ function CanvasImpl({
|
|
|
15095
14918
|
const unsubscribeErrorRef = React__namespace.useRef(null);
|
|
15096
14919
|
useIsomorphicLayoutEffect(() => {
|
|
15097
14920
|
effectActiveRef.current = true;
|
|
14921
|
+
if (fallbackVisible) return;
|
|
15098
14922
|
const canvas = canvasRef.current;
|
|
15099
14923
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15100
14924
|
if (!root.current) {
|
|
@@ -15115,24 +14939,27 @@ function CanvasImpl({
|
|
|
15115
14939
|
}
|
|
15116
14940
|
async function run() {
|
|
15117
14941
|
if (!effectActiveRef.current || !root.current) return;
|
|
15118
|
-
await root.current.configure({
|
|
14942
|
+
const configured = await root.current.configure({
|
|
14943
|
+
id,
|
|
14944
|
+
primaryCanvas,
|
|
14945
|
+
scheduler,
|
|
15119
14946
|
gl,
|
|
15120
14947
|
renderer,
|
|
15121
14948
|
scene,
|
|
15122
14949
|
events,
|
|
15123
14950
|
shadows,
|
|
15124
|
-
linear,
|
|
15125
|
-
flat,
|
|
15126
|
-
legacy,
|
|
15127
14951
|
orthographic,
|
|
15128
14952
|
frameloop,
|
|
15129
14953
|
dpr,
|
|
15130
14954
|
performance,
|
|
15131
14955
|
raycaster,
|
|
15132
14956
|
camera,
|
|
14957
|
+
autoUpdateFrustum,
|
|
14958
|
+
occlusion,
|
|
15133
14959
|
size: effectiveSize,
|
|
15134
14960
|
// Store size props for reset functionality
|
|
15135
14961
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14962
|
+
forceEven,
|
|
15136
14963
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15137
14964
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15138
14965
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15153,10 +14980,25 @@ function CanvasImpl({
|
|
|
15153
14980
|
}
|
|
15154
14981
|
onCreated?.(state);
|
|
15155
14982
|
}
|
|
15156
|
-
})
|
|
15157
|
-
|
|
14983
|
+
}).then(
|
|
14984
|
+
() => true,
|
|
14985
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
14986
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
14987
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
14988
|
+
(setupError) => {
|
|
14989
|
+
if (effectActiveRef.current) {
|
|
14990
|
+
if (fallback != null) setFallbackVisible(true);
|
|
14991
|
+
else setError(setupError);
|
|
14992
|
+
}
|
|
14993
|
+
return false;
|
|
14994
|
+
}
|
|
14995
|
+
);
|
|
14996
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15158
14997
|
root.current.render(
|
|
15159
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14998
|
+
/* @__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: [
|
|
14999
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
15000
|
+
children ?? null
|
|
15001
|
+
] }) }) })
|
|
15160
15002
|
);
|
|
15161
15003
|
}
|
|
15162
15004
|
run();
|
|
@@ -15183,20 +15025,15 @@ function CanvasImpl({
|
|
|
15183
15025
|
const canvas = canvasRef.current;
|
|
15184
15026
|
if (!canvas) return;
|
|
15185
15027
|
const handleHMR = () => {
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
rootEntry
|
|
15189
|
-
|
|
15190
|
-
uniforms: {},
|
|
15191
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15192
|
-
}));
|
|
15193
|
-
}
|
|
15028
|
+
queueMicrotask(() => {
|
|
15029
|
+
const rootEntry = _roots.get(canvas);
|
|
15030
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15031
|
+
});
|
|
15194
15032
|
};
|
|
15195
15033
|
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) {
|
|
15196
15034
|
const hot = undefined;
|
|
15197
15035
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15198
|
-
return () => hot.
|
|
15199
|
-
});
|
|
15036
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15200
15037
|
}
|
|
15201
15038
|
if (typeof module !== "undefined" && module.hot) {
|
|
15202
15039
|
const hot = module.hot;
|
|
@@ -15219,7 +15056,20 @@ function CanvasImpl({
|
|
|
15219
15056
|
...style
|
|
15220
15057
|
},
|
|
15221
15058
|
...props,
|
|
15222
|
-
children:
|
|
15059
|
+
children: fallbackVisible ? (
|
|
15060
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15061
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15062
|
+
fallback
|
|
15063
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
15064
|
+
"canvas",
|
|
15065
|
+
{
|
|
15066
|
+
ref: canvasRef,
|
|
15067
|
+
id,
|
|
15068
|
+
className: "r3f-canvas",
|
|
15069
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15070
|
+
children: fallback
|
|
15071
|
+
}
|
|
15072
|
+
) })
|
|
15223
15073
|
}
|
|
15224
15074
|
);
|
|
15225
15075
|
}
|
|
@@ -15229,15 +15079,23 @@ function Canvas(props) {
|
|
|
15229
15079
|
|
|
15230
15080
|
extend(THREE);
|
|
15231
15081
|
|
|
15082
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
15083
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15232
15084
|
exports.Block = Block;
|
|
15233
15085
|
exports.Canvas = Canvas;
|
|
15086
|
+
exports.Environment = Environment;
|
|
15087
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
15088
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
15089
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15234
15090
|
exports.ErrorBoundary = ErrorBoundary;
|
|
15091
|
+
exports.FROM_REF = FROM_REF;
|
|
15235
15092
|
exports.IsObject = IsObject;
|
|
15093
|
+
exports.ONCE = ONCE;
|
|
15094
|
+
exports.Portal = Portal;
|
|
15236
15095
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15237
15096
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15238
15097
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15239
15098
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15240
|
-
exports.Scheduler = Scheduler;
|
|
15241
15099
|
exports.Texture = Texture;
|
|
15242
15100
|
exports._roots = _roots;
|
|
15243
15101
|
exports.act = act;
|
|
@@ -15262,30 +15120,40 @@ exports.events = createPointerEvents;
|
|
|
15262
15120
|
exports.extend = extend;
|
|
15263
15121
|
exports.findInitialRoot = findInitialRoot;
|
|
15264
15122
|
exports.flushSync = flushSync;
|
|
15123
|
+
exports.fromRef = fromRef;
|
|
15265
15124
|
exports.getInstanceProps = getInstanceProps;
|
|
15125
|
+
exports.getPrimary = getPrimary;
|
|
15126
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15266
15127
|
exports.getRootState = getRootState;
|
|
15267
|
-
exports.getScheduler = getScheduler;
|
|
15268
15128
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15269
15129
|
exports.hasConstructor = hasConstructor;
|
|
15130
|
+
exports.hasPrimary = hasPrimary;
|
|
15270
15131
|
exports.invalidate = invalidate;
|
|
15271
15132
|
exports.invalidateInstance = invalidateInstance;
|
|
15272
15133
|
exports.is = is;
|
|
15273
15134
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15274
15135
|
exports.isCopyable = isCopyable;
|
|
15136
|
+
exports.isFromRef = isFromRef;
|
|
15275
15137
|
exports.isObject3D = isObject3D;
|
|
15138
|
+
exports.isOnce = isOnce;
|
|
15276
15139
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15277
15140
|
exports.isRef = isRef;
|
|
15278
15141
|
exports.isRenderer = isRenderer;
|
|
15279
15142
|
exports.isTexture = isTexture;
|
|
15280
15143
|
exports.isVectorLike = isVectorLike;
|
|
15144
|
+
exports.once = once;
|
|
15281
15145
|
exports.prepare = prepare;
|
|
15146
|
+
exports.presetsObj = presetsObj;
|
|
15282
15147
|
exports.reconciler = reconciler;
|
|
15148
|
+
exports.registerPrimary = registerPrimary;
|
|
15283
15149
|
exports.removeInteractivity = removeInteractivity;
|
|
15284
15150
|
exports.resolve = resolve;
|
|
15285
15151
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
15152
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15286
15153
|
exports.updateCamera = updateCamera;
|
|
15287
15154
|
exports.updateFrustum = updateFrustum;
|
|
15288
15155
|
exports.useBridge = useBridge;
|
|
15156
|
+
exports.useEnvironment = useEnvironment;
|
|
15289
15157
|
exports.useFrame = useFrame;
|
|
15290
15158
|
exports.useGraph = useGraph;
|
|
15291
15159
|
exports.useInstanceHandle = useInstanceHandle;
|
|
@@ -15297,3 +15165,4 @@ exports.useStore = useStore;
|
|
|
15297
15165
|
exports.useTexture = useTexture;
|
|
15298
15166
|
exports.useTextures = useTextures;
|
|
15299
15167
|
exports.useThree = useThree;
|
|
15168
|
+
exports.waitForPrimary = waitForPrimary;
|