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