@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.164c7be

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.cjs CHANGED
@@ -5,8 +5,15 @@ const jsxRuntime = require('react/jsx-runtime');
5
5
  const React = require('react');
6
6
  const useMeasure = require('react-use-measure');
7
7
  const itsFine = require('its-fine');
8
+ const fiber = require('@react-three/fiber');
9
+ const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
10
+ const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
11
+ const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
12
+ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
13
+ const gainmapJs = require('@monogrid/gainmap-js');
8
14
  const Tb = require('scheduler');
9
15
  const traditional = require('zustand/traditional');
16
+ const scheduler = require('@pmndrs/scheduler');
10
17
  const suspendReact = require('suspend-react');
11
18
 
12
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -66,9 +73,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
66
73
  WebGPURenderer: WebGPURenderer
67
74
  }, [three__namespace]);
68
75
 
69
- var __defProp$2 = Object.defineProperty;
70
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
71
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
76
+ const primaryRegistry = /* @__PURE__ */ new Map();
77
+ const pendingSubscribers = /* @__PURE__ */ new Map();
78
+ function registerPrimary(id, renderer, store) {
79
+ if (primaryRegistry.has(id)) {
80
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
81
+ }
82
+ const entry = { renderer, store };
83
+ primaryRegistry.set(id, entry);
84
+ const subscribers = pendingSubscribers.get(id);
85
+ if (subscribers) {
86
+ subscribers.forEach((callback) => callback(entry));
87
+ pendingSubscribers.delete(id);
88
+ }
89
+ return () => {
90
+ const currentEntry = primaryRegistry.get(id);
91
+ if (currentEntry?.renderer === renderer) {
92
+ primaryRegistry.delete(id);
93
+ }
94
+ };
95
+ }
96
+ function getPrimary(id) {
97
+ return primaryRegistry.get(id);
98
+ }
99
+ function waitForPrimary(id, timeout = 5e3) {
100
+ const existing = primaryRegistry.get(id);
101
+ if (existing) {
102
+ return Promise.resolve(existing);
103
+ }
104
+ return new Promise((resolve, reject) => {
105
+ const timeoutId = setTimeout(() => {
106
+ const subscribers = pendingSubscribers.get(id);
107
+ if (subscribers) {
108
+ const index = subscribers.indexOf(callback);
109
+ if (index !== -1) subscribers.splice(index, 1);
110
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
111
+ }
112
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
113
+ }, timeout);
114
+ const callback = (entry) => {
115
+ clearTimeout(timeoutId);
116
+ resolve(entry);
117
+ };
118
+ if (!pendingSubscribers.has(id)) {
119
+ pendingSubscribers.set(id, []);
120
+ }
121
+ pendingSubscribers.get(id).push(callback);
122
+ });
123
+ }
124
+ function hasPrimary(id) {
125
+ return primaryRegistry.has(id);
126
+ }
127
+ function unregisterPrimary(id) {
128
+ primaryRegistry.delete(id);
129
+ }
130
+ function getPrimaryIds() {
131
+ return Array.from(primaryRegistry.keys());
132
+ }
133
+
134
+ const presetsObj = {
135
+ apartment: "lebombo_1k.hdr",
136
+ city: "potsdamer_platz_1k.hdr",
137
+ dawn: "kiara_1_dawn_1k.hdr",
138
+ forest: "forest_slope_1k.hdr",
139
+ lobby: "st_fagans_interior_1k.hdr",
140
+ night: "dikhololo_night_1k.hdr",
141
+ park: "rooitou_park_1k.hdr",
142
+ studio: "studio_small_03_1k.hdr",
143
+ sunset: "venice_sunset_1k.hdr",
144
+ warehouse: "empty_warehouse_01_1k.hdr"
145
+ };
146
+
147
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
148
+ const isArray = (arr) => Array.isArray(arr);
149
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
150
+ function useEnvironment({
151
+ files = defaultFiles,
152
+ path = "",
153
+ preset = void 0,
154
+ colorSpace = void 0,
155
+ extensions
156
+ } = {}) {
157
+ if (preset) {
158
+ validatePreset(preset);
159
+ files = presetsObj[preset];
160
+ path = CUBEMAP_ROOT;
161
+ }
162
+ const multiFile = isArray(files);
163
+ const { extension, isCubemap } = getExtension(files);
164
+ const loader = getLoader$1(extension);
165
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
166
+ const renderer = fiber.useThree((state) => state.renderer);
167
+ React.useLayoutEffect(() => {
168
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
169
+ function clearGainmapTexture() {
170
+ fiber.useLoader.clear(loader, multiFile ? [files] : files);
171
+ }
172
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
173
+ }, [extension, files, loader, multiFile, renderer.domElement]);
174
+ const loaderResult = fiber.useLoader(
175
+ loader,
176
+ multiFile ? [files] : files,
177
+ (loader2) => {
178
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
179
+ loader2.setRenderer?.(renderer);
180
+ }
181
+ loader2.setPath?.(path);
182
+ if (extensions) extensions(loader2);
183
+ }
184
+ );
185
+ let texture = multiFile ? (
186
+ // @ts-ignore
187
+ loaderResult[0]
188
+ ) : loaderResult;
189
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
190
+ texture = texture.renderTarget?.texture;
191
+ }
192
+ texture.mapping = isCubemap ? three.CubeReflectionMapping : three.EquirectangularReflectionMapping;
193
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
194
+ return texture;
195
+ }
196
+ const preloadDefaultOptions = {
197
+ files: defaultFiles,
198
+ path: "",
199
+ preset: void 0,
200
+ extensions: void 0
201
+ };
202
+ useEnvironment.preload = (preloadOptions) => {
203
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
204
+ let { files, path = "" } = options;
205
+ const { preset, extensions } = options;
206
+ if (preset) {
207
+ validatePreset(preset);
208
+ files = presetsObj[preset];
209
+ path = CUBEMAP_ROOT;
210
+ }
211
+ const { extension } = getExtension(files);
212
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
213
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
214
+ }
215
+ const loader = getLoader$1(extension);
216
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
217
+ fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
218
+ loader2.setPath?.(path);
219
+ if (extensions) extensions(loader2);
220
+ });
221
+ };
222
+ const clearDefaultOptins = {
223
+ files: defaultFiles,
224
+ preset: void 0
225
+ };
226
+ useEnvironment.clear = (clearOptions) => {
227
+ const options = { ...clearDefaultOptins, ...clearOptions };
228
+ let { files } = options;
229
+ const { preset } = options;
230
+ if (preset) {
231
+ validatePreset(preset);
232
+ files = presetsObj[preset];
233
+ }
234
+ const { extension } = getExtension(files);
235
+ const loader = getLoader$1(extension);
236
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
237
+ fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
238
+ };
239
+ function validatePreset(preset) {
240
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
241
+ }
242
+ function getExtension(files) {
243
+ const isCubemap = isArray(files) && files.length === 6;
244
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
245
+ const firstEntry = isArray(files) ? files[0] : files;
246
+ 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();
247
+ return { extension, isCubemap, isGainmap };
248
+ }
249
+ function getLoader$1(extension) {
250
+ const loader = extension === "cube" ? three.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
251
+ return loader;
252
+ }
253
+
254
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
255
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
256
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
257
+ sceneProps = {
258
+ backgroundBlurriness: 0,
259
+ backgroundIntensity: 1,
260
+ backgroundRotation: [0, 0, 0],
261
+ environmentIntensity: 1,
262
+ environmentRotation: [0, 0, 0],
263
+ ...sceneProps
264
+ };
265
+ const target = resolveScene(scene || defaultScene);
266
+ const oldbg = target.background;
267
+ const oldenv = target.environment;
268
+ const oldSceneProps = {
269
+ // @ts-ignore
270
+ backgroundBlurriness: target.backgroundBlurriness,
271
+ // @ts-ignore
272
+ backgroundIntensity: target.backgroundIntensity,
273
+ // @ts-ignore
274
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
275
+ // @ts-ignore
276
+ environmentIntensity: target.environmentIntensity,
277
+ // @ts-ignore
278
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
279
+ };
280
+ if (background !== "only") target.environment = texture;
281
+ if (background) target.background = texture;
282
+ fiber.applyProps(target, sceneProps);
283
+ return () => {
284
+ if (background !== "only") target.environment = oldenv;
285
+ if (background) target.background = oldbg;
286
+ fiber.applyProps(target, oldSceneProps);
287
+ };
288
+ }
289
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
290
+ const defaultScene = fiber.useThree((state) => state.scene);
291
+ React__namespace.useLayoutEffect(() => {
292
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
293
+ });
294
+ return null;
295
+ }
296
+ function EnvironmentCube({
297
+ background = false,
298
+ scene,
299
+ blur,
300
+ backgroundBlurriness,
301
+ backgroundIntensity,
302
+ backgroundRotation,
303
+ environmentIntensity,
304
+ environmentRotation,
305
+ ...rest
306
+ }) {
307
+ const texture = useEnvironment(rest);
308
+ const defaultScene = fiber.useThree((state) => state.scene);
309
+ React__namespace.useLayoutEffect(() => {
310
+ return setEnvProps(background, scene, defaultScene, texture, {
311
+ backgroundBlurriness: blur ?? backgroundBlurriness,
312
+ backgroundIntensity,
313
+ backgroundRotation,
314
+ environmentIntensity,
315
+ environmentRotation
316
+ });
317
+ });
318
+ React__namespace.useEffect(() => {
319
+ return () => {
320
+ texture.dispose();
321
+ };
322
+ }, [texture]);
323
+ return null;
324
+ }
325
+ function EnvironmentPortal({
326
+ children,
327
+ near = 0.1,
328
+ far = 1e3,
329
+ resolution = 256,
330
+ frames = 1,
331
+ map,
332
+ background = false,
333
+ blur,
334
+ backgroundBlurriness,
335
+ backgroundIntensity,
336
+ backgroundRotation,
337
+ environmentIntensity,
338
+ environmentRotation,
339
+ scene,
340
+ files,
341
+ path,
342
+ preset = void 0,
343
+ extensions
344
+ }) {
345
+ const gl = fiber.useThree((state) => state.gl);
346
+ const defaultScene = fiber.useThree((state) => state.scene);
347
+ const camera = React__namespace.useRef(null);
348
+ const [virtualScene] = React__namespace.useState(() => new three.Scene());
349
+ const fbo = React__namespace.useMemo(() => {
350
+ const fbo2 = new three.WebGLCubeRenderTarget(resolution);
351
+ fbo2.texture.type = three.HalfFloatType;
352
+ return fbo2;
353
+ }, [resolution]);
354
+ React__namespace.useEffect(() => {
355
+ return () => {
356
+ fbo.dispose();
357
+ };
358
+ }, [fbo]);
359
+ React__namespace.useLayoutEffect(() => {
360
+ if (frames === 1) {
361
+ const autoClear = gl.autoClear;
362
+ gl.autoClear = true;
363
+ camera.current.update(gl, virtualScene);
364
+ gl.autoClear = autoClear;
365
+ }
366
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
367
+ backgroundBlurriness: blur ?? backgroundBlurriness,
368
+ backgroundIntensity,
369
+ backgroundRotation,
370
+ environmentIntensity,
371
+ environmentRotation
372
+ });
373
+ }, [
374
+ children,
375
+ virtualScene,
376
+ fbo.texture,
377
+ scene,
378
+ defaultScene,
379
+ background,
380
+ frames,
381
+ gl,
382
+ blur,
383
+ backgroundBlurriness,
384
+ backgroundIntensity,
385
+ backgroundRotation,
386
+ environmentIntensity,
387
+ environmentRotation
388
+ ]);
389
+ let count = 1;
390
+ fiber.useFrame(() => {
391
+ if (frames === Infinity || count < frames) {
392
+ const autoClear = gl.autoClear;
393
+ gl.autoClear = true;
394
+ camera.current.update(gl, virtualScene);
395
+ gl.autoClear = autoClear;
396
+ count++;
397
+ }
398
+ });
399
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
400
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
401
+ children,
402
+ /* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
403
+ files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
404
+ ] }),
405
+ virtualScene
406
+ ) });
407
+ }
408
+ function EnvironmentGround(props) {
409
+ const textureDefault = useEnvironment(props);
410
+ const texture = props.map || textureDefault;
411
+ React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
412
+ React__namespace.useEffect(() => {
413
+ return () => {
414
+ textureDefault.dispose();
415
+ };
416
+ }, [textureDefault]);
417
+ const height = props.ground?.height ?? 15;
418
+ const radius = props.ground?.radius ?? 60;
419
+ const scale = props.ground?.scale ?? 1e3;
420
+ const args = React__namespace.useMemo(
421
+ () => [texture, height, radius],
422
+ [texture, height, radius]
423
+ );
424
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
425
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
426
+ /* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
427
+ ] });
428
+ }
429
+ function EnvironmentColor({ color, scene }) {
430
+ const defaultScene = fiber.useThree((state) => state.scene);
431
+ React__namespace.useLayoutEffect(() => {
432
+ if (color === void 0) return;
433
+ const target = resolveScene(scene || defaultScene);
434
+ const oldBg = target.background;
435
+ target.background = new three.Color(color);
436
+ return () => {
437
+ target.background = oldBg;
438
+ };
439
+ });
440
+ return null;
441
+ }
442
+ function EnvironmentDualSource(props) {
443
+ const { backgroundFiles, ...envProps } = props;
444
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
445
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
446
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
447
+ ] });
448
+ }
449
+ function Environment(props) {
450
+ if (props.color && !props.files && !props.preset && !props.map) {
451
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
452
+ }
453
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
454
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
455
+ }
456
+ 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 });
457
+ }
458
+
459
+ var __defProp = Object.defineProperty;
460
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
461
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
72
462
  const act = React__namespace["act"];
73
463
  const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
74
464
  function useMutableCallback(fn) {
@@ -100,7 +490,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
100
490
  return _a = class extends React__namespace.Component {
101
491
  constructor() {
102
492
  super(...arguments);
103
- __publicField$2(this, "state", { error: false });
493
+ __publicField(this, "state", { error: false });
104
494
  }
105
495
  componentDidCatch(err) {
106
496
  this.props.set(err);
@@ -108,7 +498,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
108
498
  render() {
109
499
  return this.state.error ? null : this.props.children;
110
500
  }
111
- }, __publicField$2(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
501
+ }, __publicField(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
112
502
  })();
113
503
 
114
504
  const is = {
@@ -245,7 +635,8 @@ function prepare(target, root, type, props) {
245
635
  object,
246
636
  eventCount: 0,
247
637
  handlers: {},
248
- isHidden: false
638
+ isHidden: false,
639
+ deferredRefs: []
249
640
  };
250
641
  if (object) object.__r3f = instance;
251
642
  }
@@ -294,7 +685,7 @@ function createOcclusionObserverNode(store, uniform) {
294
685
  let occlusionSetupPromise = null;
295
686
  function enableOcclusion(store) {
296
687
  const state = store.getState();
297
- const { internal, renderer, rootScene } = state;
688
+ const { internal, renderer } = state;
298
689
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
299
690
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
300
691
  if (!hasOcclusionSupport) {
@@ -457,6 +848,22 @@ function hasVisibilityHandlers(handlers) {
457
848
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
458
849
  }
459
850
 
851
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
852
+ function fromRef(ref) {
853
+ return { [FROM_REF]: ref };
854
+ }
855
+ function isFromRef(value) {
856
+ return value !== null && typeof value === "object" && FROM_REF in value;
857
+ }
858
+
859
+ const ONCE = Symbol.for("@react-three/fiber.once");
860
+ function once(...args) {
861
+ return { [ONCE]: args.length ? args : true };
862
+ }
863
+ function isOnce(value) {
864
+ return value !== null && typeof value === "object" && ONCE in value;
865
+ }
866
+
460
867
  const RESERVED_PROPS = [
461
868
  "children",
462
869
  "key",
@@ -527,7 +934,7 @@ function getMemoizedPrototype(root) {
527
934
  ctor = new root.constructor();
528
935
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
529
936
  }
530
- } catch (e) {
937
+ } catch {
531
938
  }
532
939
  return ctor;
533
940
  }
@@ -573,6 +980,25 @@ function applyProps(object, props) {
573
980
  continue;
574
981
  }
575
982
  if (value === void 0) continue;
983
+ if (isFromRef(value)) {
984
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
985
+ continue;
986
+ }
987
+ if (isOnce(value)) {
988
+ if (instance?.appliedOnce?.has(prop)) continue;
989
+ if (instance) {
990
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
991
+ instance.appliedOnce.add(prop);
992
+ }
993
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
994
+ const args = value[ONCE];
995
+ if (typeof targetRoot[targetKey] === "function") {
996
+ targetRoot[targetKey](...args === true ? [] : args);
997
+ } else if (args !== true && args.length > 0) {
998
+ targetRoot[targetKey] = args[0];
999
+ }
1000
+ continue;
1001
+ }
576
1002
  let { root, key, target } = resolve(object, prop);
577
1003
  if (target === void 0 && (typeof root !== "object" || root === null)) {
578
1004
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -595,7 +1021,10 @@ function applyProps(object, props) {
595
1021
  else target.set(value);
596
1022
  } else {
597
1023
  root[key] = value;
598
- 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
1024
+ if (key.endsWith("Node") && root.isMaterial) {
1025
+ root.needsUpdate = true;
1026
+ }
1027
+ if (rootState && rootState.renderer?.outputColorSpace === three.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
599
1028
  root[key].format === three.RGBAFormat && root[key].type === three.UnsignedByteType) {
600
1029
  root[key].colorSpace = rootState.textureColorSpace;
601
1030
  }
@@ -628,38 +1057,60 @@ function applyProps(object, props) {
628
1057
  return object;
629
1058
  }
630
1059
 
1060
+ const DEFAULT_POINTER_ID = 0;
1061
+ const XR_POINTER_ID_START = 1e3;
1062
+ function getPointerState(internal, pointerId) {
1063
+ let state = internal.pointerMap.get(pointerId);
1064
+ if (!state) {
1065
+ state = {
1066
+ hovered: /* @__PURE__ */ new Map(),
1067
+ captured: /* @__PURE__ */ new Map(),
1068
+ initialClick: [0, 0],
1069
+ initialHits: []
1070
+ };
1071
+ internal.pointerMap.set(pointerId, state);
1072
+ }
1073
+ return state;
1074
+ }
1075
+ function getPointerId(event) {
1076
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1077
+ }
631
1078
  function makeId(event) {
632
1079
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
633
1080
  }
634
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
635
- const captureData = captures.get(obj);
1081
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1082
+ const pointerState = internal.pointerMap.get(pointerId);
1083
+ if (!pointerState) return;
1084
+ const captureData = pointerState.captured.get(obj);
636
1085
  if (captureData) {
637
- captures.delete(obj);
638
- if (captures.size === 0) {
639
- capturedMap.delete(pointerId);
640
- captureData.target.releasePointerCapture(pointerId);
641
- }
1086
+ pointerState.captured.delete(obj);
1087
+ captureData.target.releasePointerCapture(pointerId);
642
1088
  }
643
1089
  }
644
1090
  function removeInteractivity(store, object) {
645
1091
  const { internal } = store.getState();
646
1092
  internal.interaction = internal.interaction.filter((o) => o !== object);
647
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
648
- internal.hovered.forEach((value, key) => {
649
- if (value.eventObject === object || value.object === object) {
650
- internal.hovered.delete(key);
1093
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1094
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1095
+ pointerState.hovered.forEach((value, key) => {
1096
+ if (value.eventObject === object || value.object === object) {
1097
+ pointerState.hovered.delete(key);
1098
+ }
1099
+ });
1100
+ if (pointerState.captured.has(object)) {
1101
+ releaseInternalPointerCapture(internal, object, pointerId);
651
1102
  }
652
- });
653
- internal.capturedMap.forEach((captures, pointerId) => {
654
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
655
- });
1103
+ }
656
1104
  unregisterVisibility(store, object);
657
1105
  }
658
1106
  function createEvents(store) {
659
- function calculateDistance(event) {
1107
+ function calculateDistance(event, pointerId) {
660
1108
  const { internal } = store.getState();
661
- const dx = event.offsetX - internal.initialClick[0];
662
- const dy = event.offsetY - internal.initialClick[1];
1109
+ const pointerState = internal.pointerMap.get(pointerId);
1110
+ if (!pointerState) return 0;
1111
+ const [initialX, initialY] = pointerState.initialClick;
1112
+ const dx = event.offsetX - initialX;
1113
+ const dy = event.offsetY - initialY;
663
1114
  return Math.round(Math.sqrt(dx * dx + dy * dy));
664
1115
  }
665
1116
  function filterPointerEvents(objects) {
@@ -695,6 +1146,15 @@ function createEvents(store) {
695
1146
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
696
1147
  }
697
1148
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1149
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1150
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1151
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1152
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1153
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1154
+ if (aInteractivePriority !== bInteractivePriority) {
1155
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1156
+ }
1157
+ }
698
1158
  const aState = getRootState(a.object);
699
1159
  const bState = getRootState(b.object);
700
1160
  const aPriority = aState?.events?.priority ?? 1;
@@ -716,9 +1176,13 @@ function createEvents(store) {
716
1176
  eventObject = eventObject.parent;
717
1177
  }
718
1178
  }
719
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
720
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
721
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1179
+ if ("pointerId" in event) {
1180
+ const pointerId = event.pointerId;
1181
+ const pointerState = state.internal.pointerMap.get(pointerId);
1182
+ if (pointerState?.captured.size) {
1183
+ for (const captureData of pointerState.captured.values()) {
1184
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1185
+ }
722
1186
  }
723
1187
  }
724
1188
  return intersections;
@@ -731,27 +1195,25 @@ function createEvents(store) {
731
1195
  if (state) {
732
1196
  const { raycaster, pointer, camera, internal } = state;
733
1197
  const unprojectedPoint = new three.Vector3(pointer.x, pointer.y, 0).unproject(camera);
734
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1198
+ const hasPointerCapture = (id) => {
1199
+ const pointerState = internal.pointerMap.get(id);
1200
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1201
+ };
735
1202
  const setPointerCapture = (id) => {
736
1203
  const captureData = { intersection: hit, target: event.target };
737
- if (internal.capturedMap.has(id)) {
738
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
739
- } else {
740
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
741
- }
1204
+ const pointerState = getPointerState(internal, id);
1205
+ pointerState.captured.set(hit.eventObject, captureData);
742
1206
  event.target.setPointerCapture(id);
743
1207
  };
744
1208
  const releasePointerCapture = (id) => {
745
- const captures = internal.capturedMap.get(id);
746
- if (captures) {
747
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
748
- }
1209
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
749
1210
  };
750
1211
  const extractEventProps = {};
751
1212
  for (const prop in event) {
752
1213
  const property = event[prop];
753
1214
  if (typeof property !== "function") extractEventProps[prop] = property;
754
1215
  }
1216
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
755
1217
  const raycastEvent = {
756
1218
  ...hit,
757
1219
  ...extractEventProps,
@@ -762,18 +1224,19 @@ function createEvents(store) {
762
1224
  unprojectedPoint,
763
1225
  ray: raycaster.ray,
764
1226
  camera,
1227
+ pointerId: eventPointerId,
765
1228
  // Hijack stopPropagation, which just sets a flag
766
1229
  stopPropagation() {
767
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1230
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
768
1231
  if (
769
1232
  // ...if this pointer hasn't been captured
770
- !capturesForPointer || // ... or if the hit object is capturing the pointer
771
- capturesForPointer.has(hit.eventObject)
1233
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1234
+ pointerState.captured.has(hit.eventObject)
772
1235
  ) {
773
1236
  raycastEvent.stopped = localState.stopped = true;
774
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1237
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
775
1238
  const higher = intersections.slice(0, intersections.indexOf(hit));
776
- cancelPointer([...higher, hit]);
1239
+ cancelPointer([...higher, hit], eventPointerId);
777
1240
  }
778
1241
  }
779
1242
  },
@@ -789,15 +1252,18 @@ function createEvents(store) {
789
1252
  }
790
1253
  return intersections;
791
1254
  }
792
- function cancelPointer(intersections) {
1255
+ function cancelPointer(intersections, pointerId) {
793
1256
  const { internal } = store.getState();
794
- for (const hoveredObj of internal.hovered.values()) {
1257
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1258
+ const pointerState = internal.pointerMap.get(pid);
1259
+ if (!pointerState) return;
1260
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
795
1261
  if (!intersections.length || !intersections.find(
796
1262
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
797
1263
  )) {
798
1264
  const eventObject = hoveredObj.eventObject;
799
1265
  const instance = eventObject.__r3f;
800
- internal.hovered.delete(makeId(hoveredObj));
1266
+ pointerState.hovered.delete(hoveredId);
801
1267
  if (instance?.eventCount) {
802
1268
  const handlers = instance.handlers;
803
1269
  const data = { ...hoveredObj, intersections };
@@ -826,41 +1292,118 @@ function createEvents(store) {
826
1292
  instance?.handlers.onDropMissed?.(event);
827
1293
  }
828
1294
  }
1295
+ function cleanupPointer(pointerId) {
1296
+ const { internal } = store.getState();
1297
+ const pointerState = internal.pointerMap.get(pointerId);
1298
+ if (pointerState) {
1299
+ for (const [, hoveredObj] of pointerState.hovered) {
1300
+ const eventObject = hoveredObj.eventObject;
1301
+ const instance = eventObject.__r3f;
1302
+ if (instance?.eventCount) {
1303
+ const handlers = instance.handlers;
1304
+ const data = { ...hoveredObj, intersections: [] };
1305
+ handlers.onPointerOut?.(data);
1306
+ handlers.onPointerLeave?.(data);
1307
+ }
1308
+ }
1309
+ internal.pointerMap.delete(pointerId);
1310
+ }
1311
+ internal.pointerDirty.delete(pointerId);
1312
+ }
1313
+ function processDeferredPointer(event, pointerId) {
1314
+ const state = store.getState();
1315
+ const { internal } = state;
1316
+ if (!state.events.enabled) return;
1317
+ const filter = filterPointerEvents;
1318
+ const hits = intersect(event, filter);
1319
+ cancelPointer(hits, pointerId);
1320
+ function onIntersect(data) {
1321
+ const eventObject = data.eventObject;
1322
+ const instance = eventObject.__r3f;
1323
+ if (!instance?.eventCount) return;
1324
+ const handlers = instance.handlers;
1325
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1326
+ const id = makeId(data);
1327
+ const pointerState = getPointerState(internal, pointerId);
1328
+ const hoveredItem = pointerState.hovered.get(id);
1329
+ if (!hoveredItem) {
1330
+ pointerState.hovered.set(id, data);
1331
+ handlers.onPointerOver?.(data);
1332
+ handlers.onPointerEnter?.(data);
1333
+ } else if (hoveredItem.stopped) {
1334
+ data.stopPropagation();
1335
+ }
1336
+ }
1337
+ handlers.onPointerMove?.(data);
1338
+ }
1339
+ handleIntersects(hits, event, 0, onIntersect);
1340
+ }
829
1341
  function handlePointer(name) {
830
1342
  switch (name) {
831
1343
  case "onPointerLeave":
832
- case "onPointerCancel":
833
1344
  case "onDragLeave":
834
1345
  return () => cancelPointer([]);
1346
+ // Global cancel of these events
1347
+ case "onPointerCancel":
1348
+ return (event) => {
1349
+ const pointerId = getPointerId(event);
1350
+ cleanupPointer(pointerId);
1351
+ };
835
1352
  case "onLostPointerCapture":
836
1353
  return (event) => {
837
1354
  const { internal } = store.getState();
838
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1355
+ const pointerId = getPointerId(event);
1356
+ const pointerState = internal.pointerMap.get(pointerId);
1357
+ if (pointerState?.captured.size) {
839
1358
  requestAnimationFrame(() => {
840
- if (internal.capturedMap.has(event.pointerId)) {
841
- internal.capturedMap.delete(event.pointerId);
842
- cancelPointer([]);
1359
+ const pointerState2 = internal.pointerMap.get(pointerId);
1360
+ if (pointerState2?.captured.size) {
1361
+ pointerState2.captured.clear();
843
1362
  }
1363
+ cancelPointer([], pointerId);
844
1364
  });
845
1365
  }
846
1366
  };
847
1367
  }
848
1368
  return function handleEvent(event) {
849
1369
  const state = store.getState();
850
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1370
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1371
+ const pointerId = getPointerId(event);
851
1372
  internal.lastEvent.current = event;
852
- if (!state.events.enabled) return;
1373
+ if (!events.enabled) return;
853
1374
  const isPointerMove = name === "onPointerMove";
854
1375
  const isDragOver = name === "onDragOver";
855
1376
  const isDrop = name === "onDrop";
856
1377
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1378
+ const isPointerDown = name === "onPointerDown";
1379
+ const isPointerUp = name === "onPointerUp";
1380
+ const isWheel = name === "onWheel";
1381
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1382
+ if (isPointerMove && canDeferRaycasts) {
1383
+ events.compute?.(event, state);
1384
+ internal.pointerDirty.set(pointerId, event);
1385
+ return;
1386
+ }
1387
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1388
+ events.compute?.(event, state);
1389
+ internal.pointerDirty.set(pointerId, event);
1390
+ return;
1391
+ }
1392
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1393
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1394
+ internal.pointerDirty.delete(pointerId);
1395
+ processDeferredPointer(deferredEvent, pointerId);
1396
+ }
857
1397
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
858
1398
  const hits = intersect(event, filter);
859
- const delta = isClickEvent ? calculateDistance(event) : 0;
860
- if (name === "onPointerDown") {
861
- internal.initialClick = [event.offsetX, event.offsetY];
862
- internal.initialHits = hits.map((hit) => hit.eventObject);
863
- }
1399
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1400
+ if (isPointerDown) {
1401
+ const pointerState2 = getPointerState(internal, pointerId);
1402
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1403
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1404
+ }
1405
+ const pointerState = internal.pointerMap.get(pointerId);
1406
+ const initialHits = pointerState?.initialHits ?? [];
864
1407
  if (isClickEvent && !hits.length) {
865
1408
  if (delta <= 2) {
866
1409
  pointerMissed(event, internal.interaction);
@@ -875,7 +1418,9 @@ function createEvents(store) {
875
1418
  dropMissed(event, internal.interaction);
876
1419
  if (onDropMissed) onDropMissed(event);
877
1420
  }
878
- if (isPointerMove || isDragOver) cancelPointer(hits);
1421
+ if (isPointerMove || isDragOver) {
1422
+ cancelPointer(hits, pointerId);
1423
+ }
879
1424
  function onIntersect(data) {
880
1425
  const eventObject = data.eventObject;
881
1426
  const instance = eventObject.__r3f;
@@ -884,9 +1429,10 @@ function createEvents(store) {
884
1429
  if (isPointerMove) {
885
1430
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
886
1431
  const id = makeId(data);
887
- const hoveredItem = internal.hovered.get(id);
1432
+ const pointerState2 = getPointerState(internal, pointerId);
1433
+ const hoveredItem = pointerState2.hovered.get(id);
888
1434
  if (!hoveredItem) {
889
- internal.hovered.set(id, data);
1435
+ pointerState2.hovered.set(id, data);
890
1436
  handlers.onPointerOver?.(data);
891
1437
  handlers.onPointerEnter?.(data);
892
1438
  } else if (hoveredItem.stopped) {
@@ -896,9 +1442,10 @@ function createEvents(store) {
896
1442
  handlers.onPointerMove?.(data);
897
1443
  } else if (isDragOver) {
898
1444
  const id = makeId(data);
899
- const hoveredItem = internal.hovered.get(id);
1445
+ const pointerState2 = getPointerState(internal, pointerId);
1446
+ const hoveredItem = pointerState2.hovered.get(id);
900
1447
  if (!hoveredItem) {
901
- internal.hovered.set(id, data);
1448
+ pointerState2.hovered.set(id, data);
902
1449
  handlers.onDragOverEnter?.(data);
903
1450
  } else if (hoveredItem.stopped) {
904
1451
  data.stopPropagation();
@@ -909,18 +1456,18 @@ function createEvents(store) {
909
1456
  } else {
910
1457
  const handler = handlers[name];
911
1458
  if (handler) {
912
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1459
+ if (!isClickEvent || initialHits.includes(eventObject)) {
913
1460
  pointerMissed(
914
1461
  event,
915
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1462
+ internal.interaction.filter((object) => !initialHits.includes(object))
916
1463
  );
917
1464
  handler(data);
918
1465
  }
919
1466
  } else {
920
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1467
+ if (isClickEvent && initialHits.includes(eventObject)) {
921
1468
  pointerMissed(
922
1469
  event,
923
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1470
+ internal.interaction.filter((object) => !initialHits.includes(object))
924
1471
  );
925
1472
  }
926
1473
  }
@@ -929,7 +1476,15 @@ function createEvents(store) {
929
1476
  handleIntersects(hits, event, delta, onIntersect);
930
1477
  };
931
1478
  }
932
- return { handlePointer };
1479
+ function flushDeferredPointers() {
1480
+ const { internal, events } = store.getState();
1481
+ if (!events.frameTimedRaycasts) return;
1482
+ for (const [pointerId, event] of internal.pointerDirty) {
1483
+ processDeferredPointer(event, pointerId);
1484
+ }
1485
+ internal.pointerDirty.clear();
1486
+ }
1487
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
933
1488
  }
934
1489
  const DOM_EVENTS = {
935
1490
  onClick: ["click", false],
@@ -948,11 +1503,16 @@ const DOM_EVENTS = {
948
1503
  onLostPointerCapture: ["lostpointercapture", true]
949
1504
  };
950
1505
  function createPointerEvents(store) {
951
- const { handlePointer } = createEvents(store);
1506
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1507
+ let nextXRPointerId = XR_POINTER_ID_START;
1508
+ const xrPointers = /* @__PURE__ */ new Map();
952
1509
  return {
953
1510
  priority: 1,
954
1511
  enabled: true,
955
- compute(event, state, previous) {
1512
+ frameTimedRaycasts: true,
1513
+ alwaysFireOnScroll: true,
1514
+ updateOnFrame: false,
1515
+ compute(event, state) {
956
1516
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
957
1517
  state.raycaster.setFromCamera(state.pointer, state.camera);
958
1518
  },
@@ -961,11 +1521,33 @@ function createPointerEvents(store) {
961
1521
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
962
1522
  {}
963
1523
  ),
964
- update: () => {
1524
+ update: (pointerId) => {
1525
+ const { events, internal } = store.getState();
1526
+ if (!events.handlers) return;
1527
+ if (pointerId !== void 0) {
1528
+ const event = internal.pointerDirty.get(pointerId);
1529
+ if (event) {
1530
+ internal.pointerDirty.delete(pointerId);
1531
+ processDeferredPointer(event, pointerId);
1532
+ } else if (internal.lastEvent?.current) {
1533
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1534
+ }
1535
+ } else {
1536
+ flushDeferredPointers();
1537
+ if (internal.lastEvent?.current) {
1538
+ events.handlers.onPointerMove(internal.lastEvent.current);
1539
+ }
1540
+ }
1541
+ },
1542
+ flush: () => {
965
1543
  const { events, internal } = store.getState();
966
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1544
+ flushDeferredPointers();
1545
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1546
+ events.handlers.onPointerMove(internal.lastEvent.current);
1547
+ }
967
1548
  },
968
1549
  connect: (target) => {
1550
+ if (!target) return;
969
1551
  const { set, events } = store.getState();
970
1552
  events.disconnect?.();
971
1553
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -989,6 +1571,32 @@ function createPointerEvents(store) {
989
1571
  }
990
1572
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
991
1573
  }
1574
+ },
1575
+ registerPointer: (config) => {
1576
+ const pointerId = nextXRPointerId++;
1577
+ xrPointers.set(pointerId, config);
1578
+ const { internal } = store.getState();
1579
+ getPointerState(internal, pointerId);
1580
+ return pointerId;
1581
+ },
1582
+ unregisterPointer: (pointerId) => {
1583
+ xrPointers.delete(pointerId);
1584
+ const { internal } = store.getState();
1585
+ const pointerState = internal.pointerMap.get(pointerId);
1586
+ if (pointerState) {
1587
+ for (const [, hoveredObj] of pointerState.hovered) {
1588
+ const eventObject = hoveredObj.eventObject;
1589
+ const instance = eventObject.__r3f;
1590
+ if (instance?.eventCount) {
1591
+ const handlers = instance.handlers;
1592
+ const data = { ...hoveredObj, intersections: [] };
1593
+ handlers.onPointerOut?.(data);
1594
+ handlers.onPointerLeave?.(data);
1595
+ }
1596
+ }
1597
+ internal.pointerMap.delete(pointerId);
1598
+ }
1599
+ internal.pointerDirty.delete(pointerId);
992
1600
  }
993
1601
  };
994
1602
  }
@@ -1079,23 +1687,29 @@ const createStore = (invalidate, advance) => {
1079
1687
  set,
1080
1688
  get,
1081
1689
  // Mock objects that have to be configured
1690
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
1691
+ primaryStore: null,
1082
1692
  gl: null,
1083
1693
  renderer: null,
1084
1694
  camera: null,
1085
1695
  frustum: new three.Frustum(),
1086
1696
  autoUpdateFrustum: true,
1087
1697
  raycaster: null,
1088
- events: { priority: 1, enabled: true, connected: false },
1698
+ events: {
1699
+ priority: 1,
1700
+ enabled: true,
1701
+ connected: false,
1702
+ frameTimedRaycasts: true,
1703
+ alwaysFireOnScroll: true,
1704
+ updateOnFrame: false
1705
+ },
1089
1706
  scene: null,
1090
1707
  rootScene: null,
1091
1708
  xr: null,
1092
1709
  inspector: null,
1093
1710
  invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1094
1711
  advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1095
- legacy: false,
1096
- linear: false,
1097
- flat: false,
1098
- textureColorSpace: "srgb",
1712
+ textureColorSpace: three.SRGBColorSpace,
1099
1713
  isLegacy: false,
1100
1714
  webGPUSupported: false,
1101
1715
  isNative: false,
@@ -1153,6 +1767,7 @@ const createStore = (invalidate, advance) => {
1153
1767
  size: newSize,
1154
1768
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1155
1769
  }));
1770
+ scheduler.getScheduler().invalidate();
1156
1771
  }
1157
1772
  }
1158
1773
  return;
@@ -1167,6 +1782,7 @@ const createStore = (invalidate, advance) => {
1167
1782
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1168
1783
  _sizeImperative: true
1169
1784
  }));
1785
+ scheduler.getScheduler().invalidate();
1170
1786
  },
1171
1787
  setDpr: (dpr) => set((state2) => {
1172
1788
  const resolved = calculateDpr(dpr);
@@ -1177,11 +1793,14 @@ const createStore = (invalidate, advance) => {
1177
1793
  },
1178
1794
  setError: (error) => set(() => ({ error })),
1179
1795
  error: null,
1180
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1796
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
1181
1797
  uniforms: {},
1182
1798
  nodes: {},
1799
+ buffers: {},
1800
+ gpuStorage: {},
1183
1801
  textures: /* @__PURE__ */ new Map(),
1184
- postProcessing: null,
1802
+ _textureRefs: /* @__PURE__ */ new Map(),
1803
+ renderPipeline: null,
1185
1804
  passes: {},
1186
1805
  _hmrVersion: 0,
1187
1806
  _sizeImperative: false,
@@ -1190,12 +1809,16 @@ const createStore = (invalidate, advance) => {
1190
1809
  internal: {
1191
1810
  // Events
1192
1811
  interaction: [],
1193
- hovered: /* @__PURE__ */ new Map(),
1194
1812
  subscribers: [],
1813
+ // Per-pointer state (new unified structure)
1814
+ pointerMap: /* @__PURE__ */ new Map(),
1815
+ pointerDirty: /* @__PURE__ */ new Map(),
1816
+ lastEvent: React__namespace.createRef(),
1817
+ // Deprecated but kept for backwards compatibility
1818
+ hovered: /* @__PURE__ */ new Map(),
1195
1819
  initialClick: [0, 0],
1196
1820
  initialHits: [],
1197
1821
  capturedMap: /* @__PURE__ */ new Map(),
1198
- lastEvent: React__namespace.createRef(),
1199
1822
  // Visibility tracking (onFramed, onOccluded, onVisible)
1200
1823
  visibilityRegistry: /* @__PURE__ */ new Map(),
1201
1824
  // Occlusion system (WebGPU only)
@@ -1278,13 +1901,22 @@ const createStore = (invalidate, advance) => {
1278
1901
  rootStore.subscribe(() => {
1279
1902
  const { camera, size, viewport, set, internal } = rootStore.getState();
1280
1903
  const actualRenderer = internal.actualRenderer;
1904
+ const canvasTarget = internal.canvasTarget;
1281
1905
  if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1282
1906
  oldSize = size;
1283
1907
  oldDpr = viewport.dpr;
1284
1908
  updateCamera(camera, size);
1285
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1286
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1287
- actualRenderer.setSize(size.width, size.height, updateStyle);
1909
+ if (internal.isSecondary && canvasTarget) {
1910
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1911
+ canvasTarget.setSize(size.width, size.height, false);
1912
+ } else {
1913
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1914
+ actualRenderer.setSize(size.width, size.height, false);
1915
+ if (canvasTarget) {
1916
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1917
+ canvasTarget.setSize(size.width, size.height, false);
1918
+ }
1919
+ }
1288
1920
  }
1289
1921
  if (camera !== oldCamera) {
1290
1922
  oldCamera = camera;
@@ -1355,1042 +1987,10 @@ useLoader.clear = function(loader, input) {
1355
1987
  };
1356
1988
  useLoader.loader = getLoader;
1357
1989
 
1358
- var __defProp$1 = Object.defineProperty;
1359
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1360
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1361
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1362
- class PhaseGraph {
1363
- constructor() {
1364
- /** Ordered list of phase nodes */
1365
- __publicField$1(this, "phases", []);
1366
- /** Quick lookup by name */
1367
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1368
- /** Cached ordered names (invalidated on changes) */
1369
- __publicField$1(this, "orderedNamesCache", null);
1370
- this.initializeDefaultPhases();
1371
- }
1372
- //* Initialization --------------------------------
1373
- initializeDefaultPhases() {
1374
- for (const name of DEFAULT_PHASES) {
1375
- const node = { name, isAutoGenerated: false };
1376
- this.phases.push(node);
1377
- this.phaseMap.set(name, node);
1378
- }
1379
- this.invalidateCache();
1380
- }
1381
- //* Public API --------------------------------
1382
- /**
1383
- * Add a named phase to the graph
1384
- * @param name - Phase name (must be unique)
1385
- * @param options - Position options (before or after another phase)
1386
- */
1387
- addPhase(name, options = {}) {
1388
- if (this.phaseMap.has(name)) {
1389
- console.warn(`[useFrame] Phase "${name}" already exists`);
1390
- return;
1391
- }
1392
- const { before, after } = options;
1393
- const node = { name, isAutoGenerated: false };
1394
- let insertIndex = this.phases.length;
1395
- const targetIndex = this.getPhaseIndex(before ?? after);
1396
- if (targetIndex !== -1) {
1397
- insertIndex = before ? targetIndex : targetIndex + 1;
1398
- } else {
1399
- const constraintType = before ? "before" : "after";
1400
- console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
1401
- }
1402
- this.phases.splice(insertIndex, 0, node);
1403
- this.phaseMap.set(name, node);
1404
- this.invalidateCache();
1405
- }
1406
- /**
1407
- * Get ordered list of phase names
1408
- */
1409
- getOrderedPhases() {
1410
- if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
1411
- return this.orderedNamesCache;
1412
- }
1413
- /**
1414
- * Check if a phase exists
1415
- */
1416
- hasPhase(name) {
1417
- return this.phaseMap.has(name);
1418
- }
1419
- /**
1420
- * Get the index of a phase (-1 if not found)
1421
- */
1422
- getPhaseIndex(name) {
1423
- if (!name) return -1;
1424
- return this.phases.findIndex((p) => p.name === name);
1425
- }
1426
- /**
1427
- * Ensure a phase exists, creating an auto-generated one if needed.
1428
- * Used for resolving before/after constraints.
1429
- *
1430
- * @param name - The phase name to ensure exists
1431
- * @returns The phase name (may be auto-generated like 'before:render')
1432
- */
1433
- ensurePhase(name) {
1434
- if (this.phaseMap.has(name)) return name;
1435
- const node = { name, isAutoGenerated: true };
1436
- this.phases.push(node);
1437
- this.phaseMap.set(name, node);
1438
- this.invalidateCache();
1439
- return name;
1440
- }
1441
- /**
1442
- * Resolve where a job with before/after constraints should go.
1443
- * Creates auto-generated phases if needed.
1444
- *
1445
- * @param before - Phase(s) to run before
1446
- * @param after - Phase(s) to run after
1447
- * @returns The resolved phase name
1448
- */
1449
- resolveConstraintPhase(before, after) {
1450
- const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
1451
- const afterArr = after ? Array.isArray(after) ? after : [after] : [];
1452
- if (beforeArr.length > 0) {
1453
- return this.ensureAutoPhase(beforeArr[0], "before", 0);
1454
- }
1455
- if (afterArr.length > 0) {
1456
- return this.ensureAutoPhase(afterArr[0], "after", 1);
1457
- }
1458
- return "update";
1459
- }
1460
- /**
1461
- * Ensure an auto-generated phase exists relative to a target phase.
1462
- * Creates the phase if it doesn't exist, inserting it at the correct position.
1463
- *
1464
- * @param target - The target phase name to position relative to
1465
- * @param prefix - Prefix for auto-generated phase name ('before' or 'after')
1466
- * @param offset - Insertion offset (0 for before, 1 for after)
1467
- * @returns The auto-generated phase name
1468
- */
1469
- ensureAutoPhase(target, prefix, offset) {
1470
- const autoName = `${prefix}:${target}`;
1471
- if (this.phaseMap.has(autoName)) return autoName;
1472
- const node = { name: autoName, isAutoGenerated: true };
1473
- const targetIndex = this.getPhaseIndex(target);
1474
- if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
1475
- else this.phases.push(node);
1476
- this.phaseMap.set(autoName, node);
1477
- this.invalidateCache();
1478
- return autoName;
1479
- }
1480
- // Internal --------------------------------
1481
- invalidateCache() {
1482
- this.orderedNamesCache = null;
1483
- }
1484
- }
1485
-
1486
- function rebuildSortedJobs(jobs, phaseGraph) {
1487
- const orderedPhases = phaseGraph.getOrderedPhases();
1488
- const buckets = /* @__PURE__ */ new Map();
1489
- for (const phase of orderedPhases) {
1490
- buckets.set(phase, []);
1491
- }
1492
- for (const job of jobs.values()) {
1493
- if (!job.enabled) continue;
1494
- let bucket = buckets.get(job.phase);
1495
- if (!bucket) {
1496
- bucket = [];
1497
- buckets.set(job.phase, bucket);
1498
- }
1499
- bucket.push(job);
1500
- }
1501
- const sortedBuckets = [];
1502
- for (const phase of orderedPhases) {
1503
- const bucket = buckets.get(phase);
1504
- if (!bucket || bucket.length === 0) continue;
1505
- bucket.sort((a, b) => {
1506
- if (a.priority !== b.priority) return b.priority - a.priority;
1507
- return a.index - b.index;
1508
- });
1509
- sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
1510
- }
1511
- for (const [phase, bucket] of buckets) {
1512
- if (!orderedPhases.includes(phase) && bucket.length > 0) {
1513
- bucket.sort((a, b) => {
1514
- if (a.priority !== b.priority) return b.priority - a.priority;
1515
- return a.index - b.index;
1516
- });
1517
- sortedBuckets.push(bucket);
1518
- }
1519
- }
1520
- return sortedBuckets.flat();
1521
- }
1522
- function hasCrossJobConstraints(bucket) {
1523
- const jobIds = new Set(bucket.map((j) => j.id));
1524
- for (const job of bucket) {
1525
- for (const ref of job.before) {
1526
- if (jobIds.has(ref)) return true;
1527
- }
1528
- for (const ref of job.after) {
1529
- if (jobIds.has(ref)) return true;
1530
- }
1531
- }
1532
- return false;
1533
- }
1534
- function topologicalSort(jobs) {
1535
- const n = jobs.length;
1536
- if (n <= 1) return jobs;
1537
- const jobMap = /* @__PURE__ */ new Map();
1538
- const inDegree = /* @__PURE__ */ new Map();
1539
- const adjacency = /* @__PURE__ */ new Map();
1540
- for (const job of jobs) {
1541
- jobMap.set(job.id, job);
1542
- inDegree.set(job.id, 0);
1543
- adjacency.set(job.id, []);
1544
- }
1545
- for (const job of jobs) {
1546
- for (const ref of job.before) {
1547
- if (jobMap.has(ref)) {
1548
- adjacency.get(job.id).push(ref);
1549
- inDegree.set(ref, inDegree.get(ref) + 1);
1550
- }
1551
- }
1552
- for (const ref of job.after) {
1553
- if (jobMap.has(ref)) {
1554
- adjacency.get(ref).push(job.id);
1555
- inDegree.set(job.id, inDegree.get(job.id) + 1);
1556
- }
1557
- }
1558
- }
1559
- const queue = [];
1560
- for (const job of jobs) {
1561
- if (inDegree.get(job.id) === 0) {
1562
- queue.push(job);
1563
- }
1564
- }
1565
- queue.sort((a, b) => {
1566
- if (a.priority !== b.priority) return b.priority - a.priority;
1567
- return a.index - b.index;
1568
- });
1569
- const result = [];
1570
- while (queue.length > 0) {
1571
- const job = queue.shift();
1572
- result.push(job);
1573
- const neighbors = adjacency.get(job.id) || [];
1574
- for (const neighborId of neighbors) {
1575
- const newDegree = inDegree.get(neighborId) - 1;
1576
- inDegree.set(neighborId, newDegree);
1577
- if (newDegree === 0) {
1578
- const neighbor = jobMap.get(neighborId);
1579
- insertSorted(queue, neighbor);
1580
- }
1581
- }
1582
- }
1583
- if (result.length !== n) {
1584
- console.warn("[useFrame] Circular dependency detected in job constraints");
1585
- const resultIds = new Set(result.map((j) => j.id));
1586
- for (const job of jobs) {
1587
- if (!resultIds.has(job.id)) result.push(job);
1588
- }
1589
- }
1590
- return result;
1591
- }
1592
- function insertSorted(arr, job) {
1593
- let i = 0;
1594
- while (i < arr.length) {
1595
- const cmp = arr[i];
1596
- if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
1597
- break;
1598
- }
1599
- i++;
1600
- }
1601
- arr.splice(i, 0, job);
1602
- }
1603
-
1604
- function shouldRun(job, now) {
1605
- if (!job.enabled) return false;
1606
- if (!job.fps) return true;
1607
- const minInterval = 1e3 / job.fps;
1608
- const lastRun = job.lastRun ?? 0;
1609
- const elapsed = now - lastRun;
1610
- if (elapsed < minInterval) return false;
1611
- if (job.drop) {
1612
- job.lastRun = now;
1613
- } else {
1614
- const steps = Math.floor(elapsed / minInterval);
1615
- job.lastRun = lastRun + steps * minInterval;
1616
- if (job.lastRun < now - minInterval) {
1617
- job.lastRun = now;
1618
- }
1619
- }
1620
- return true;
1621
- }
1622
- function resetJobTiming(job) {
1623
- job.lastRun = void 0;
1624
- }
1625
-
1626
- var __defProp = Object.defineProperty;
1627
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1628
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1629
- const hmrData = (() => {
1630
- if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
1631
- if (typeof import_meta_hot !== "undefined") return import_meta_hot;
1632
- try {
1633
- return (0, eval)("import.meta.hot");
1634
- } catch {
1635
- return void 0;
1636
- }
1637
- })();
1638
- const _Scheduler = class _Scheduler {
1639
- //* Constructor ================================
1640
- constructor() {
1641
- //* Critical State ================================
1642
- __publicField(this, "roots", /* @__PURE__ */ new Map());
1643
- __publicField(this, "phaseGraph");
1644
- __publicField(this, "loopState", {
1645
- running: false,
1646
- rafHandle: null,
1647
- lastTime: null,
1648
- // null = uninitialized, 0+ = valid timestamp
1649
- frameCount: 0,
1650
- elapsedTime: 0,
1651
- createdAt: performance.now()
1652
- });
1653
- __publicField(this, "stoppedTime", 0);
1654
- //* Private State ================================
1655
- __publicField(this, "nextRootIndex", 0);
1656
- __publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
1657
- __publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
1658
- __publicField(this, "nextGlobalIndex", 0);
1659
- __publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
1660
- __publicField(this, "nextJobIndex", 0);
1661
- __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1662
- __publicField(this, "pendingFrames", 0);
1663
- __publicField(this, "_frameloop", "always");
1664
- //* Independent Mode & Error Handling State ================================
1665
- __publicField(this, "_independent", false);
1666
- __publicField(this, "errorHandler", null);
1667
- __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1668
- //* Core Loop Execution Methods ================================
1669
- /**
1670
- * Main RAF loop callback.
1671
- * Executes frame, handles demand mode, and schedules next frame.
1672
- * @param {number} timestamp - RAF timestamp in milliseconds
1673
- * @returns {void}
1674
- * @private
1675
- */
1676
- __publicField(this, "loop", (timestamp) => {
1677
- if (!this.loopState.running) return;
1678
- this.executeFrame(timestamp);
1679
- if (this._frameloop === "demand") {
1680
- this.pendingFrames = Math.max(0, this.pendingFrames - 1);
1681
- if (this.pendingFrames === 0) {
1682
- this.notifyIdle(timestamp);
1683
- return this.stop();
1684
- }
1685
- }
1686
- this.loopState.rafHandle = requestAnimationFrame(this.loop);
1687
- });
1688
- this.phaseGraph = new PhaseGraph();
1689
- }
1690
- static get instance() {
1691
- return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1692
- }
1693
- static set instance(value) {
1694
- globalThis[_Scheduler.INSTANCE_KEY] = value;
1695
- }
1696
- /**
1697
- * Get the global scheduler instance (creates if doesn't exist).
1698
- * Uses HMR data to preserve instance across hot reloads.
1699
- * @returns {Scheduler} The singleton scheduler instance
1700
- */
1701
- static get() {
1702
- if (!_Scheduler.instance && hmrData?.data?.scheduler) {
1703
- _Scheduler.instance = hmrData.data.scheduler;
1704
- }
1705
- if (!_Scheduler.instance) {
1706
- _Scheduler.instance = new _Scheduler();
1707
- if (hmrData?.data) {
1708
- hmrData.data.scheduler = _Scheduler.instance;
1709
- }
1710
- }
1711
- return _Scheduler.instance;
1712
- }
1713
- /**
1714
- * Reset the singleton instance. Stops the loop and clears all state.
1715
- * Primarily used for testing to ensure clean state between tests.
1716
- * @returns {void}
1717
- */
1718
- static reset() {
1719
- if (_Scheduler.instance) {
1720
- _Scheduler.instance.stop();
1721
- _Scheduler.instance = null;
1722
- }
1723
- if (hmrData?.data) {
1724
- hmrData.data.scheduler = null;
1725
- }
1726
- }
1727
- //* Getters & Setters ================================
1728
- get phases() {
1729
- return this.phaseGraph.getOrderedPhases();
1730
- }
1731
- get frameloop() {
1732
- return this._frameloop;
1733
- }
1734
- set frameloop(mode) {
1735
- if (this._frameloop === mode) return;
1736
- const wasAlways = this._frameloop === "always";
1737
- this._frameloop = mode;
1738
- if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
1739
- else if (mode !== "always" && wasAlways) this.stop();
1740
- }
1741
- get isRunning() {
1742
- return this.loopState.running;
1743
- }
1744
- get isReady() {
1745
- return this.roots.size > 0;
1746
- }
1747
- get independent() {
1748
- return this._independent;
1749
- }
1750
- set independent(value) {
1751
- this._independent = value;
1752
- if (value) this.ensureDefaultRoot();
1753
- }
1754
- //* Root Management Methods ================================
1755
- /**
1756
- * Register a root (Canvas) with the scheduler.
1757
- * The first root to register starts the RAF loop (if frameloop='always').
1758
- * @param {string} id - Unique identifier for this root
1759
- * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1760
- * @returns {() => void} Unsubscribe function to remove this root
1761
- */
1762
- registerRoot(id, options = {}) {
1763
- if (this.roots.has(id)) {
1764
- console.warn(`[Scheduler] Root "${id}" already registered`);
1765
- return () => this.unregisterRoot(id);
1766
- }
1767
- const entry = {
1768
- id,
1769
- getState: options.getState ?? (() => ({})),
1770
- jobs: /* @__PURE__ */ new Map(),
1771
- sortedJobs: [],
1772
- needsRebuild: false
1773
- };
1774
- if (options.onError) {
1775
- this.errorHandler = options.onError;
1776
- }
1777
- this.roots.set(id, entry);
1778
- if (this.roots.size === 1) {
1779
- this.notifyRootReady();
1780
- if (this._frameloop === "always") this.start();
1781
- }
1782
- return () => this.unregisterRoot(id);
1783
- }
1784
- /**
1785
- * Unregister a root from the scheduler.
1786
- * Cleans up all job state listeners for this root's jobs.
1787
- * The last root to unregister stops the RAF loop.
1788
- * @param {string} id - The root ID to unregister
1789
- * @returns {void}
1790
- */
1791
- unregisterRoot(id) {
1792
- const root = this.roots.get(id);
1793
- if (!root) return;
1794
- for (const jobId of root.jobs.keys()) {
1795
- this.jobStateListeners.delete(jobId);
1796
- }
1797
- this.roots.delete(id);
1798
- if (this.roots.size === 0) {
1799
- this.stop();
1800
- this.errorHandler = null;
1801
- }
1802
- }
1803
- /**
1804
- * Subscribe to be notified when a root becomes available.
1805
- * Fires immediately if a root already exists.
1806
- * @param {() => void} callback - Function called when first root registers
1807
- * @returns {() => void} Unsubscribe function
1808
- */
1809
- onRootReady(callback) {
1810
- if (this.roots.size > 0) {
1811
- callback();
1812
- return () => {
1813
- };
1814
- }
1815
- this.rootReadyCallbacks.add(callback);
1816
- return () => this.rootReadyCallbacks.delete(callback);
1817
- }
1818
- /**
1819
- * Notify all registered root-ready callbacks.
1820
- * Called when the first root registers.
1821
- * @returns {void}
1822
- * @private
1823
- */
1824
- notifyRootReady() {
1825
- for (const cb of this.rootReadyCallbacks) {
1826
- try {
1827
- cb();
1828
- } catch (error) {
1829
- console.error("[Scheduler] Error in root-ready callback:", error);
1830
- }
1831
- }
1832
- this.rootReadyCallbacks.clear();
1833
- }
1834
- /**
1835
- * Ensure a default root exists for independent mode.
1836
- * Creates a minimal root with no state provider.
1837
- * @returns {void}
1838
- * @private
1839
- */
1840
- ensureDefaultRoot() {
1841
- if (!this.roots.has("__default__")) {
1842
- this.registerRoot("__default__");
1843
- }
1844
- }
1845
- /**
1846
- * Trigger error handling for job errors.
1847
- * Uses the bound error handler if available, otherwise logs to console.
1848
- * @param {Error} error - The error to handle
1849
- * @returns {void}
1850
- */
1851
- triggerError(error) {
1852
- if (this.errorHandler) this.errorHandler(error);
1853
- else console.error("[Scheduler]", error);
1854
- }
1855
- //* Phase Management Methods ================================
1856
- /**
1857
- * Add a named phase to the scheduler's execution order.
1858
- * Marks all roots for rebuild to incorporate the new phase.
1859
- * @param {string} name - The phase name (e.g., 'physics', 'postprocess')
1860
- * @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
1861
- * @returns {void}
1862
- * @example
1863
- * scheduler.addPhase('physics', { before: 'update' });
1864
- * scheduler.addPhase('postprocess', { after: 'render' });
1865
- */
1866
- addPhase(name, options) {
1867
- this.phaseGraph.addPhase(name, options);
1868
- for (const root of this.roots.values()) {
1869
- root.needsRebuild = true;
1870
- }
1871
- }
1872
- /**
1873
- * Check if a phase exists in the scheduler.
1874
- * @param {string} name - The phase name to check
1875
- * @returns {boolean} True if the phase exists
1876
- */
1877
- hasPhase(name) {
1878
- return this.phaseGraph.hasPhase(name);
1879
- }
1880
- //* Global Job Registration Methods (Deprecated APIs) ================================
1881
- /**
1882
- * Register a global job that runs once per frame (not per-root).
1883
- * Used internally by deprecated addEffect/addAfterEffect APIs.
1884
- * @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
1885
- * @param {string} id - Unique identifier for this global job
1886
- * @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
1887
- * @returns {() => void} Unsubscribe function to remove this global job
1888
- * @deprecated Use useFrame with phases instead
1889
- */
1890
- registerGlobal(phase, id, callback) {
1891
- const job = { id, callback };
1892
- if (phase === "before") {
1893
- this.globalBeforeJobs.set(id, job);
1894
- } else {
1895
- this.globalAfterJobs.set(id, job);
1896
- }
1897
- return () => {
1898
- if (phase === "before") this.globalBeforeJobs.delete(id);
1899
- else this.globalAfterJobs.delete(id);
1900
- };
1901
- }
1902
- //* Idle Callback Methods (Deprecated API) ================================
1903
- /**
1904
- * Register an idle callback that fires when the loop stops.
1905
- * Used internally by deprecated addTail API.
1906
- * @param {(timestamp: number) => void} callback - Function called when loop becomes idle
1907
- * @returns {() => void} Unsubscribe function to remove this idle callback
1908
- * @deprecated Use demand mode with invalidate() instead
1909
- */
1910
- onIdle(callback) {
1911
- this.idleCallbacks.add(callback);
1912
- return () => this.idleCallbacks.delete(callback);
1913
- }
1914
- /**
1915
- * Notify all registered idle callbacks.
1916
- * Called when the loop stops in demand mode.
1917
- * @param {number} timestamp - The RAF timestamp when idle occurred
1918
- * @returns {void}
1919
- * @private
1920
- */
1921
- notifyIdle(timestamp) {
1922
- for (const cb of this.idleCallbacks) {
1923
- try {
1924
- cb(timestamp);
1925
- } catch (error) {
1926
- console.error("[Scheduler] Error in idle callback:", error);
1927
- }
1928
- }
1929
- }
1930
- //* Job Registration & Management Methods ================================
1931
- /**
1932
- * Register a job (frame callback) with a specific root.
1933
- * This is the core registration method used by useFrame internally.
1934
- * @param {FrameNextCallback} callback - The function to call each frame
1935
- * @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
1936
- * @param {string} [options.rootId] - Target root ID (defaults to first registered root)
1937
- * @param {string} [options.id] - Unique job ID (auto-generated if not provided)
1938
- * @param {string} [options.phase] - Execution phase (defaults to 'update')
1939
- * @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
1940
- * @param {number} [options.fps] - FPS throttle limit
1941
- * @param {boolean} [options.drop] - Drop frames when behind (default true)
1942
- * @param {boolean} [options.enabled] - Whether job is active (default true)
1943
- * @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
1944
- * @returns {() => void} Unsubscribe function to remove this job
1945
- */
1946
- register(callback, options = {}) {
1947
- const rootId = options.rootId;
1948
- const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
1949
- if (!root) {
1950
- console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
1951
- return () => {
1952
- };
1953
- }
1954
- const id = options.id ?? this.generateJobId();
1955
- let phase = options.phase ?? "update";
1956
- if (!options.phase && (options.before || options.after)) {
1957
- phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
1958
- }
1959
- const before = this.normalizeConstraints(options.before);
1960
- const after = this.normalizeConstraints(options.after);
1961
- const job = {
1962
- id,
1963
- callback,
1964
- phase,
1965
- before,
1966
- after,
1967
- priority: options.priority ?? 0,
1968
- index: this.nextJobIndex++,
1969
- fps: options.fps,
1970
- drop: options.drop ?? true,
1971
- enabled: options.enabled ?? true,
1972
- system: options.system ?? false
1973
- };
1974
- if (root.jobs.has(id)) {
1975
- console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
1976
- }
1977
- root.jobs.set(id, job);
1978
- root.needsRebuild = true;
1979
- return () => this.unregister(id, root.id);
1980
- }
1981
- /**
1982
- * Unregister a job by its ID.
1983
- * Searches all roots if rootId is not provided.
1984
- * @param {string} id - The job ID to unregister
1985
- * @param {string} [rootId] - Optional root ID to search (searches all if not provided)
1986
- * @returns {void}
1987
- */
1988
- unregister(id, rootId) {
1989
- const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
1990
- if (root?.jobs.delete(id)) {
1991
- root.needsRebuild = true;
1992
- this.jobStateListeners.delete(id);
1993
- }
1994
- }
1995
- /**
1996
- * Update a job's options dynamically.
1997
- * Searches all roots to find the job by ID.
1998
- * Phase/constraint changes trigger a rebuild of the sorted job list.
1999
- * @param {string} id - The job ID to update
2000
- * @param {Partial<JobOptions>} options - The options to update
2001
- * @returns {void}
2002
- */
2003
- updateJob(id, options) {
2004
- let job;
2005
- let root;
2006
- for (const r of this.roots.values()) {
2007
- job = r.jobs.get(id);
2008
- if (job) {
2009
- root = r;
2010
- break;
2011
- }
2012
- }
2013
- if (!job || !root) return;
2014
- if (options.priority !== void 0) job.priority = options.priority;
2015
- if (options.fps !== void 0) job.fps = options.fps;
2016
- if (options.drop !== void 0) job.drop = options.drop;
2017
- if (options.enabled !== void 0) {
2018
- const wasEnabled = job.enabled;
2019
- job.enabled = options.enabled;
2020
- if (!wasEnabled && job.enabled) resetJobTiming(job);
2021
- if (wasEnabled !== job.enabled) root.needsRebuild = true;
2022
- }
2023
- if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
2024
- if (options.phase) job.phase = options.phase;
2025
- if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
2026
- if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
2027
- root.needsRebuild = true;
2028
- }
2029
- }
2030
- //* Job State Management Methods ================================
2031
- /**
2032
- * Check if a job is currently paused (disabled).
2033
- * @param {string} id - The job ID to check
2034
- * @returns {boolean} True if the job exists and is paused
2035
- */
2036
- isJobPaused(id) {
2037
- for (const root of this.roots.values()) {
2038
- const job = root.jobs.get(id);
2039
- if (job) return !job.enabled;
2040
- }
2041
- return false;
2042
- }
2043
- /**
2044
- * Subscribe to state changes for a specific job.
2045
- * Listener is called when job is paused or resumed.
2046
- * @param {string} id - The job ID to subscribe to
2047
- * @param {() => void} listener - Callback invoked on state changes
2048
- * @returns {() => void} Unsubscribe function
2049
- */
2050
- subscribeJobState(id, listener) {
2051
- if (!this.jobStateListeners.has(id)) {
2052
- this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
2053
- }
2054
- this.jobStateListeners.get(id).add(listener);
2055
- return () => {
2056
- this.jobStateListeners.get(id)?.delete(listener);
2057
- if (this.jobStateListeners.get(id)?.size === 0) {
2058
- this.jobStateListeners.delete(id);
2059
- }
2060
- };
2061
- }
2062
- /**
2063
- * Notify all listeners that a job's state has changed.
2064
- * @param {string} id - The job ID that changed
2065
- * @returns {void}
2066
- * @private
2067
- */
2068
- notifyJobStateChange(id) {
2069
- this.jobStateListeners.get(id)?.forEach((listener) => listener());
2070
- }
2071
- /**
2072
- * Pause a job by ID (sets enabled=false).
2073
- * Notifies any subscribed state listeners.
2074
- * @param {string} id - The job ID to pause
2075
- * @returns {void}
2076
- */
2077
- pauseJob(id) {
2078
- this.updateJob(id, { enabled: false });
2079
- this.notifyJobStateChange(id);
2080
- }
2081
- /**
2082
- * Resume a paused job by ID (sets enabled=true).
2083
- * Resets job timing to prevent frame accumulation.
2084
- * Notifies any subscribed state listeners.
2085
- * @param {string} id - The job ID to resume
2086
- * @returns {void}
2087
- */
2088
- resumeJob(id) {
2089
- this.updateJob(id, { enabled: true });
2090
- this.notifyJobStateChange(id);
2091
- }
2092
- //* Frame Loop Control Methods ================================
2093
- /**
2094
- * Start the requestAnimationFrame loop.
2095
- * Resets timing state (elapsedTime, frameCount) on start.
2096
- * No-op if already running.
2097
- * @returns {void}
2098
- */
2099
- start() {
2100
- if (this.loopState.running) return;
2101
- const { elapsedTime, createdAt } = this.loopState;
2102
- let adjustedCreated = 0;
2103
- if (this.stoppedTime > 0) {
2104
- adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
2105
- this.stoppedTime = 0;
2106
- }
2107
- Object.assign(this.loopState, {
2108
- running: true,
2109
- elapsedTime: elapsedTime ?? 0,
2110
- lastTime: performance.now(),
2111
- createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
2112
- frameCount: 0,
2113
- rafHandle: requestAnimationFrame(this.loop)
2114
- });
2115
- }
2116
- /**
2117
- * Stop the requestAnimationFrame loop.
2118
- * Cancels any pending RAF callback.
2119
- * No-op if not running.
2120
- * @returns {void}
2121
- */
2122
- stop() {
2123
- if (!this.loopState.running) return;
2124
- this.loopState.running = false;
2125
- if (this.loopState.rafHandle !== null) {
2126
- cancelAnimationFrame(this.loopState.rafHandle);
2127
- this.loopState.rafHandle = null;
2128
- }
2129
- this.stoppedTime = performance.now();
2130
- }
2131
- /**
2132
- * Request frames to be rendered in demand mode.
2133
- * Accumulates pending frames (capped at 60) and starts the loop if not running.
2134
- * No-op if frameloop is not 'demand'.
2135
- * @param {number} [frames=1] - Number of frames to request
2136
- * @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
2137
- * - `false` (default): Sets pending frames to the specified value (replaces existing count)
2138
- * - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
2139
- * @returns {void}
2140
- * @example
2141
- * // Request a single frame render
2142
- * scheduler.invalidate();
2143
- *
2144
- * @example
2145
- * // Request 5 frames (e.g., for animations)
2146
- * scheduler.invalidate(5);
2147
- *
2148
- * @example
2149
- * // Set pending frames to exactly 3 (don't stack with existing)
2150
- * scheduler.invalidate(3, false);
2151
- *
2152
- * @example
2153
- * // Add 2 more frames to existing pending count
2154
- * scheduler.invalidate(2, true);
2155
- */
2156
- invalidate(frames = 1, stackFrames = false) {
2157
- if (this._frameloop !== "demand") return;
2158
- const baseFrames = stackFrames ? this.pendingFrames : 0;
2159
- this.pendingFrames = Math.min(60, baseFrames + frames);
2160
- if (!this.loopState.running && this.pendingFrames > 0) this.start();
2161
- }
2162
- /**
2163
- * Reset timing state for deterministic testing.
2164
- * Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
2165
- * @returns {void}
2166
- */
2167
- resetTiming() {
2168
- this.loopState.lastTime = null;
2169
- this.loopState.frameCount = 0;
2170
- this.loopState.elapsedTime = 0;
2171
- this.loopState.createdAt = performance.now();
2172
- }
2173
- //* Manual Stepping Methods ================================
2174
- /**
2175
- * Manually execute a single frame for all roots.
2176
- * Useful for frameloop='never' mode or testing scenarios.
2177
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2178
- * @returns {void}
2179
- * @example
2180
- * // Manual control mode
2181
- * scheduler.frameloop = 'never';
2182
- * scheduler.step(); // Execute one frame
2183
- */
2184
- step(timestamp) {
2185
- const now = timestamp ?? performance.now();
2186
- this.executeFrame(now);
2187
- }
2188
- /**
2189
- * Manually execute a single job by its ID.
2190
- * Useful for testing individual job callbacks in isolation.
2191
- * @param {string} id - The job ID to step
2192
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2193
- * @returns {void}
2194
- */
2195
- stepJob(id, timestamp) {
2196
- let job;
2197
- let root;
2198
- for (const r of this.roots.values()) {
2199
- job = r.jobs.get(id);
2200
- if (job) {
2201
- root = r;
2202
- break;
2203
- }
2204
- }
2205
- if (!job || !root) {
2206
- console.warn(`[Scheduler] Job "${id}" not found`);
2207
- return;
2208
- }
2209
- const now = timestamp ?? performance.now();
2210
- const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
2211
- const delta = deltaMs / 1e3;
2212
- const elapsed = now - this.loopState.createdAt;
2213
- const providedState = root.getState?.() ?? {};
2214
- const frameState = {
2215
- ...providedState,
2216
- time: now,
2217
- delta,
2218
- elapsed,
2219
- frame: this.loopState.frameCount
2220
- };
2221
- try {
2222
- job.callback(frameState, delta);
2223
- } catch (error) {
2224
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2225
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2226
- }
2227
- }
2228
- /**
2229
- * Execute a single frame across all roots.
2230
- * Order: globalBefore → each root's jobs → globalAfter
2231
- * @param {number} timestamp - RAF timestamp in milliseconds
2232
- * @returns {void}
2233
- * @private
2234
- */
2235
- executeFrame(timestamp) {
2236
- const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
2237
- const delta = deltaMs / 1e3;
2238
- this.loopState.lastTime = timestamp;
2239
- this.loopState.frameCount++;
2240
- this.loopState.elapsedTime += deltaMs;
2241
- this.runGlobalJobs(this.globalBeforeJobs, timestamp);
2242
- for (const root of this.roots.values()) {
2243
- this.tickRoot(root, timestamp, delta);
2244
- }
2245
- this.runGlobalJobs(this.globalAfterJobs, timestamp);
2246
- }
2247
- /**
2248
- * Run all global jobs from a job map.
2249
- * Catches and logs errors without stopping execution.
2250
- * @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
2251
- * @param {number} timestamp - RAF timestamp in milliseconds
2252
- * @returns {void}
2253
- * @private
2254
- */
2255
- runGlobalJobs(jobs, timestamp) {
2256
- for (const job of jobs.values()) {
2257
- try {
2258
- job.callback(timestamp);
2259
- } catch (error) {
2260
- console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2261
- }
2262
- }
2263
- }
2264
- /**
2265
- * Execute all jobs for a single root in sorted order.
2266
- * Rebuilds sorted job list if needed, then dispatches each job.
2267
- * Errors are caught and propagated via triggerError.
2268
- * @param {RootEntry} root - The root entry to tick
2269
- * @param {number} timestamp - RAF timestamp in milliseconds
2270
- * @param {number} delta - Time since last frame in seconds
2271
- * @returns {void}
2272
- * @private
2273
- */
2274
- tickRoot(root, timestamp, delta) {
2275
- if (root.needsRebuild) {
2276
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2277
- root.needsRebuild = false;
2278
- }
2279
- const providedState = root.getState?.() ?? {};
2280
- const frameState = {
2281
- ...providedState,
2282
- time: timestamp,
2283
- delta,
2284
- elapsed: this.loopState.elapsedTime / 1e3,
2285
- // Convert ms to seconds
2286
- frame: this.loopState.frameCount
2287
- };
2288
- for (const job of root.sortedJobs) {
2289
- if (!shouldRun(job, timestamp)) continue;
2290
- try {
2291
- job.callback(frameState, delta);
2292
- } catch (error) {
2293
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2294
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2295
- }
2296
- }
2297
- }
2298
- //* Debug & Inspection Methods ================================
2299
- /**
2300
- * Get the total number of registered jobs across all roots.
2301
- * Includes both per-root jobs and global before/after jobs.
2302
- * @returns {number} Total job count
2303
- */
2304
- getJobCount() {
2305
- let count = 0;
2306
- for (const root of this.roots.values()) {
2307
- count += root.jobs.size;
2308
- }
2309
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2310
- }
2311
- /**
2312
- * Get all registered job IDs across all roots.
2313
- * Includes both per-root jobs and global before/after jobs.
2314
- * @returns {string[]} Array of all job IDs
2315
- */
2316
- getJobIds() {
2317
- const ids = [];
2318
- for (const root of this.roots.values()) {
2319
- ids.push(...root.jobs.keys());
2320
- }
2321
- ids.push(...this.globalBeforeJobs.keys());
2322
- ids.push(...this.globalAfterJobs.keys());
2323
- return ids;
2324
- }
2325
- /**
2326
- * Get the number of registered roots (Canvas instances).
2327
- * @returns {number} Number of registered roots
2328
- */
2329
- getRootCount() {
2330
- return this.roots.size;
2331
- }
2332
- /**
2333
- * Check if any user (non-system) jobs are registered in a specific phase.
2334
- * Used by the default render job to know if a user has taken over rendering.
2335
- *
2336
- * @param phase The phase to check
2337
- * @param rootId Optional root ID to check (checks all roots if not provided)
2338
- * @returns true if any user jobs exist in the phase
2339
- */
2340
- hasUserJobsInPhase(phase, rootId) {
2341
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2342
- return rootsToCheck.some((root) => {
2343
- if (!root) return false;
2344
- for (const job of root.jobs.values()) {
2345
- if (job.phase === phase && !job.system && job.enabled) return true;
2346
- }
2347
- return false;
2348
- });
2349
- }
2350
- //* Utility Methods ================================
2351
- /**
2352
- * Generate a unique root ID for automatic root registration.
2353
- * @returns {string} A unique root ID in the format 'root_N'
2354
- */
2355
- generateRootId() {
2356
- return `root_${this.nextRootIndex++}`;
2357
- }
2358
- /**
2359
- * Generate a unique job ID.
2360
- * @returns {string} A unique job ID in the format 'job_N'
2361
- * @private
2362
- */
2363
- generateJobId() {
2364
- return `job_${this.nextJobIndex}`;
2365
- }
2366
- /**
2367
- * Normalize before/after constraints to a Set.
2368
- * Handles undefined, single string, or array inputs.
2369
- * @param {string | string[] | undefined} value - The constraint value(s)
2370
- * @returns {Set<string>} Normalized Set of constraint strings
2371
- * @private
2372
- */
2373
- normalizeConstraints(value) {
2374
- if (!value) return /* @__PURE__ */ new Set();
2375
- if (Array.isArray(value)) return new Set(value);
2376
- return /* @__PURE__ */ new Set([value]);
2377
- }
2378
- };
2379
- //* Static State & Methods (Singleton Usage) ================================
2380
- //* Cross-Bundle Singleton Key ==============================
2381
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2382
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2383
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2384
- let Scheduler = _Scheduler;
2385
- const getScheduler = () => Scheduler.get();
2386
- if (hmrData) {
2387
- hmrData.accept?.();
2388
- }
2389
-
2390
1990
  function useFrame(callback, priorityOrOptions) {
2391
1991
  const store = React__namespace.useContext(context);
2392
1992
  const isInsideCanvas = store !== null;
2393
- const scheduler = getScheduler();
1993
+ const scheduler$1 = scheduler.getScheduler();
2394
1994
  const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2395
1995
  id: priorityOrOptions.id,
2396
1996
  phase: priorityOrOptions.phase,
@@ -2438,7 +2038,7 @@ function useFrame(callback, priorityOrOptions) {
2438
2038
  };
2439
2039
  callbackRef.current?.(mergedState, delta);
2440
2040
  };
2441
- const unregister = scheduler.register(wrappedCallback, {
2041
+ const unregister = scheduler$1.register(wrappedCallback, {
2442
2042
  id,
2443
2043
  rootId,
2444
2044
  ...options
@@ -2459,37 +2059,31 @@ function useFrame(callback, priorityOrOptions) {
2459
2059
  }
2460
2060
  };
2461
2061
  } else {
2462
- const registerOutside = () => {
2463
- return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2464
- };
2465
- if (scheduler.independent || scheduler.isReady) {
2466
- return registerOutside();
2467
- }
2468
- let unregisterJob = null;
2469
- const unsubReady = scheduler.onRootReady(() => {
2470
- unregisterJob = registerOutside();
2471
- });
2472
- return () => {
2473
- unsubReady();
2474
- unregisterJob?.();
2475
- };
2062
+ return scheduler$1.register(
2063
+ (state, delta) => {
2064
+ const frameState = state;
2065
+ if (!frameState.renderer) return;
2066
+ callbackRef.current?.(frameState, delta);
2067
+ },
2068
+ { id, ...options }
2069
+ );
2476
2070
  }
2477
- }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2071
+ }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2478
2072
  const isPaused = React__namespace.useSyncExternalStore(
2479
2073
  // Subscribe function
2480
2074
  React__namespace.useCallback(
2481
2075
  (onStoreChange) => {
2482
- return getScheduler().subscribeJobState(id, onStoreChange);
2076
+ return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
2483
2077
  },
2484
2078
  [id]
2485
2079
  ),
2486
2080
  // getSnapshot function
2487
- React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
2081
+ React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
2488
2082
  // getServerSnapshot function (SSR)
2489
2083
  React__namespace.useCallback(() => false, [])
2490
2084
  );
2491
2085
  const controls = React__namespace.useMemo(() => {
2492
- const scheduler2 = getScheduler();
2086
+ const scheduler2 = scheduler.getScheduler();
2493
2087
  return {
2494
2088
  /** The job's unique ID */
2495
2089
  id,
@@ -2504,7 +2098,7 @@ function useFrame(callback, priorityOrOptions) {
2504
2098
  * @param timestamp Optional timestamp (defaults to performance.now())
2505
2099
  */
2506
2100
  step: (timestamp) => {
2507
- getScheduler().stepJob(id, timestamp);
2101
+ scheduler.getScheduler().stepJob(id, timestamp);
2508
2102
  },
2509
2103
  /**
2510
2104
  * Manually step ALL jobs in the scheduler.
@@ -2512,20 +2106,20 @@ function useFrame(callback, priorityOrOptions) {
2512
2106
  * @param timestamp Optional timestamp (defaults to performance.now())
2513
2107
  */
2514
2108
  stepAll: (timestamp) => {
2515
- getScheduler().step(timestamp);
2109
+ scheduler.getScheduler().step(timestamp);
2516
2110
  },
2517
2111
  /**
2518
2112
  * Pause this job (set enabled=false).
2519
2113
  * Job remains registered but won't run.
2520
2114
  */
2521
2115
  pause: () => {
2522
- getScheduler().pauseJob(id);
2116
+ scheduler.getScheduler().pauseJob(id);
2523
2117
  },
2524
2118
  /**
2525
2119
  * Resume this job (set enabled=true).
2526
2120
  */
2527
2121
  resume: () => {
2528
- getScheduler().resumeJob(id);
2122
+ scheduler.getScheduler().resumeJob(id);
2529
2123
  },
2530
2124
  /**
2531
2125
  * Reactive paused state - automatically updates when pause/resume is called.
@@ -2563,22 +2157,29 @@ function buildFromCache(input, textureCache) {
2563
2157
  function useTexture(input, optionsOrOnLoad) {
2564
2158
  const renderer = useThree((state) => state.internal.actualRenderer);
2565
2159
  const store = useStore();
2566
- const textureCache = useThree((state) => state.textures);
2567
2160
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2568
- const { onLoad, cache = false } = options;
2161
+ const { onLoad, cache = true } = options;
2162
+ const onLoadRef = React.useRef(onLoad);
2163
+ onLoadRef.current = onLoad;
2164
+ const onLoadCalledForRef = React.useRef(null);
2569
2165
  const urls = React.useMemo(() => getUrls(input), [input]);
2570
2166
  const cachedResult = React.useMemo(() => {
2571
2167
  if (!cache) return null;
2572
- if (!allUrlsCached(urls, textureCache)) return null;
2573
- return buildFromCache(input, textureCache);
2574
- }, [cache, urls, textureCache, input]);
2168
+ const textures = store.getState().textures;
2169
+ if (!allUrlsCached(urls, textures)) return null;
2170
+ return buildFromCache(input, textures);
2171
+ }, [cache, urls, input, store]);
2575
2172
  const loadedTextures = useLoader(
2576
2173
  three.TextureLoader,
2577
2174
  IsObject(input) ? Object.values(input) : input
2578
2175
  );
2176
+ const inputKey = urls.join("\0");
2579
2177
  React.useLayoutEffect(() => {
2580
- if (!cachedResult) onLoad?.(loadedTextures);
2581
- }, [onLoad, cachedResult, loadedTextures]);
2178
+ if (cachedResult) return;
2179
+ if (onLoadCalledForRef.current === inputKey) return;
2180
+ onLoadCalledForRef.current = inputKey;
2181
+ onLoadRef.current?.(loadedTextures);
2182
+ }, [cachedResult, loadedTextures, inputKey]);
2582
2183
  React.useEffect(() => {
2583
2184
  if (cachedResult) return;
2584
2185
  if ("initTexture" in renderer) {
@@ -2611,8 +2212,6 @@ function useTexture(input, optionsOrOnLoad) {
2611
2212
  }, [input, loadedTextures, cachedResult]);
2612
2213
  React.useEffect(() => {
2613
2214
  if (!cache) return;
2614
- if (cachedResult) return;
2615
- const set = store.setState;
2616
2215
  const urlTextureMap = [];
2617
2216
  if (typeof input === "string") {
2618
2217
  urlTextureMap.push([input, mappedTextures]);
@@ -2626,18 +2225,32 @@ function useTexture(input, optionsOrOnLoad) {
2626
2225
  urlTextureMap.push([url, textureRecord[key]]);
2627
2226
  }
2628
2227
  }
2629
- set((state) => {
2630
- const newMap = new Map(state.textures);
2631
- let changed = false;
2228
+ store.setState((state) => {
2229
+ const refs = new Map(state._textureRefs);
2230
+ let textures = state.textures;
2231
+ let added = false;
2632
2232
  for (const [url, texture] of urlTextureMap) {
2633
- if (!newMap.has(url)) {
2634
- newMap.set(url, texture);
2635
- changed = true;
2233
+ if (!textures.has(url)) {
2234
+ if (!added) {
2235
+ textures = new Map(textures);
2236
+ added = true;
2237
+ }
2238
+ textures.set(url, texture);
2636
2239
  }
2240
+ refs.set(url, (refs.get(url) ?? 0) + 1);
2637
2241
  }
2638
- return changed ? { textures: newMap } : state;
2242
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
2639
2243
  });
2640
- }, [cache, input, mappedTextures, store, cachedResult]);
2244
+ return () => store.setState((state) => {
2245
+ const refs = new Map(state._textureRefs);
2246
+ for (const [url] of urlTextureMap) {
2247
+ const next = (refs.get(url) ?? 0) - 1;
2248
+ if (next <= 0) refs.delete(url);
2249
+ else refs.set(url, next);
2250
+ }
2251
+ return { _textureRefs: refs };
2252
+ });
2253
+ }, [cache, input, mappedTextures, store]);
2641
2254
  return mappedTextures;
2642
2255
  }
2643
2256
  useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
@@ -2653,106 +2266,90 @@ const Texture = ({
2653
2266
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
2654
2267
  };
2655
2268
 
2656
- function getTextureValue(entry) {
2657
- if (entry instanceof three.Texture) return entry;
2658
- if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof three.Texture) {
2659
- return entry.value;
2660
- }
2661
- return null;
2662
- }
2663
- function useTextures() {
2269
+ function useTextures(selector) {
2664
2270
  const store = useStore();
2665
- return React.useMemo(() => {
2666
- const set = store.setState;
2271
+ const registry = React.useMemo(() => {
2667
2272
  const getState = store.getState;
2668
- const add = (key, value) => {
2669
- set((state) => {
2670
- const newMap = new Map(state.textures);
2671
- newMap.set(key, value);
2672
- return { textures: newMap };
2673
- });
2674
- };
2675
- const addMultiple = (items) => {
2676
- set((state) => {
2677
- const newMap = new Map(state.textures);
2678
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
2679
- for (const [key, value] of entries) {
2680
- newMap.set(key, value);
2681
- }
2682
- return { textures: newMap };
2683
- });
2684
- };
2685
- const remove = (key) => {
2686
- set((state) => {
2687
- const newMap = new Map(state.textures);
2688
- newMap.delete(key);
2689
- return { textures: newMap };
2690
- });
2691
- };
2692
- const removeMultiple = (keys) => {
2693
- set((state) => {
2694
- const newMap = new Map(state.textures);
2695
- for (const key of keys) newMap.delete(key);
2696
- return { textures: newMap };
2697
- });
2698
- };
2699
- const dispose = (key) => {
2700
- const entry = getState().textures.get(key);
2701
- if (entry) {
2702
- const tex = getTextureValue(entry);
2703
- tex?.dispose();
2704
- }
2705
- remove(key);
2706
- };
2707
- const disposeMultiple = (keys) => {
2708
- const textures = getState().textures;
2709
- for (const key of keys) {
2710
- const entry = textures.get(key);
2711
- if (entry) {
2712
- const tex = getTextureValue(entry);
2713
- tex?.dispose();
2714
- }
2715
- }
2716
- removeMultiple(keys);
2717
- };
2718
- const disposeAll = () => {
2719
- const textures = getState().textures;
2720
- for (const entry of textures.values()) {
2721
- const tex = getTextureValue(entry);
2722
- tex?.dispose();
2723
- }
2724
- set({ textures: /* @__PURE__ */ new Map() });
2725
- };
2273
+ const setState = store.setState;
2274
+ const getOne = (key) => getState().textures.get(key);
2726
2275
  return {
2727
- // Getter for the textures Map (reactive via getState)
2728
- get textures() {
2276
+ get all() {
2729
2277
  return getState().textures;
2730
2278
  },
2731
- // Read
2732
- get: (key) => getState().textures.get(key),
2279
+ get(input) {
2280
+ if (typeof input === "string") return getOne(input);
2281
+ if (Array.isArray(input)) return input.map(getOne);
2282
+ const out = {};
2283
+ for (const name in input) out[name] = getOne(input[name]);
2284
+ return out;
2285
+ },
2733
2286
  has: (key) => getState().textures.has(key),
2734
- // Write
2735
- add,
2736
- addMultiple,
2737
- // Remove (cache only)
2738
- remove,
2739
- removeMultiple,
2740
- // Dispose (GPU + cache)
2741
- dispose,
2742
- disposeMultiple,
2743
- disposeAll
2287
+ add(keyOrRecord, texture) {
2288
+ setState((state) => {
2289
+ const textures = new Map(state.textures);
2290
+ if (typeof keyOrRecord === "string") {
2291
+ textures.set(keyOrRecord, texture);
2292
+ } else {
2293
+ for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
2294
+ }
2295
+ return { textures };
2296
+ });
2297
+ },
2298
+ dispose(key, options) {
2299
+ const state = getState();
2300
+ const refs = state._textureRefs.get(key) ?? 0;
2301
+ if (refs > 0 && !options?.force) {
2302
+ console.warn(
2303
+ `[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
2304
+ );
2305
+ return false;
2306
+ }
2307
+ state.textures.get(key)?.dispose();
2308
+ setState((s) => {
2309
+ const textures = new Map(s.textures);
2310
+ textures.delete(key);
2311
+ const nextRefs = new Map(s._textureRefs);
2312
+ nextRefs.delete(key);
2313
+ return { textures, _textureRefs: nextRefs };
2314
+ });
2315
+ return true;
2316
+ },
2317
+ disposeAll() {
2318
+ for (const texture of getState().textures.values()) texture.dispose();
2319
+ setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
2320
+ }
2744
2321
  };
2745
2322
  }, [store]);
2323
+ const subscribe = selector ? () => selector(registry) : (state) => state.textures;
2324
+ const selected = useThree(subscribe);
2325
+ return selector ? selected : registry;
2746
2326
  }
2747
2327
 
2748
- function useRenderTarget(width, height, options) {
2328
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2749
2329
  const isLegacy = useThree((s) => s.isLegacy);
2750
2330
  const size = useThree((s) => s.size);
2331
+ let width;
2332
+ let height;
2333
+ let opts;
2334
+ if (typeof widthOrOptions === "object") {
2335
+ opts = widthOrOptions;
2336
+ } else if (typeof widthOrOptions === "number") {
2337
+ width = widthOrOptions;
2338
+ if (typeof heightOrOptions === "object") {
2339
+ height = widthOrOptions;
2340
+ opts = heightOrOptions;
2341
+ } else if (typeof heightOrOptions === "number") {
2342
+ height = heightOrOptions;
2343
+ opts = options;
2344
+ } else {
2345
+ height = widthOrOptions;
2346
+ }
2347
+ }
2751
2348
  return React.useMemo(() => {
2752
2349
  const w = width ?? size.width;
2753
2350
  const h = height ?? size.height;
2754
- return new three.WebGLRenderTarget(w, h, options);
2755
- }, [width, height, size.width, size.height, options, isLegacy]);
2351
+ return new three.WebGLRenderTarget(w, h, opts);
2352
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2756
2353
  }
2757
2354
 
2758
2355
  function useStore() {
@@ -2780,7 +2377,7 @@ function addEffect(callback) {
2780
2377
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2781
2378
  });
2782
2379
  const id = `legacy_effect_${effectId++}`;
2783
- return getScheduler().registerGlobal("before", id, callback);
2380
+ return scheduler.getScheduler().registerGlobal("before", id, callback);
2784
2381
  }
2785
2382
  function addAfterEffect(callback) {
2786
2383
  notifyDepreciated({
@@ -2789,7 +2386,7 @@ function addAfterEffect(callback) {
2789
2386
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2790
2387
  });
2791
2388
  const id = `legacy_afterEffect_${effectId++}`;
2792
- return getScheduler().registerGlobal("after", id, callback);
2389
+ return scheduler.getScheduler().registerGlobal("after", id, callback);
2793
2390
  }
2794
2391
  function addTail(callback) {
2795
2392
  notifyDepreciated({
@@ -2797,13 +2394,13 @@ function addTail(callback) {
2797
2394
  body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
2798
2395
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2799
2396
  });
2800
- return getScheduler().onIdle(callback);
2397
+ return scheduler.getScheduler().onIdle(callback);
2801
2398
  }
2802
2399
  function invalidate(state, frames = 1, stackFrames = false) {
2803
- getScheduler().invalidate(frames, stackFrames);
2400
+ scheduler.getScheduler().invalidate(frames, stackFrames);
2804
2401
  }
2805
- function advance(timestamp, runGlobalEffects = true, state, frame) {
2806
- getScheduler().step(timestamp);
2402
+ function advance(timestamp) {
2403
+ scheduler.getScheduler().step(timestamp);
2807
2404
  }
2808
2405
 
2809
2406
  const version = "10.0.0-alpha.2";
@@ -14256,6 +13853,7 @@ function swapInstances() {
14256
13853
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14257
13854
  instance.object.__r3f = instance;
14258
13855
  setFiberRef(fiber, instance.object);
13856
+ delete instance.appliedOnce;
14259
13857
  applyProps(instance.object, instance.props);
14260
13858
  if (instance.props.attach) {
14261
13859
  attach(parent, instance);
@@ -14329,8 +13927,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14329
13927
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14330
13928
  if (isTailSibling) swapInstances();
14331
13929
  },
14332
- finalizeInitialChildren: () => false,
14333
- commitMount() {
13930
+ finalizeInitialChildren: (instance) => {
13931
+ for (const prop in instance.props) {
13932
+ if (isFromRef(instance.props[prop])) return true;
13933
+ }
13934
+ return false;
13935
+ },
13936
+ commitMount(instance) {
13937
+ const resolved = {};
13938
+ for (const prop in instance.props) {
13939
+ const value = instance.props[prop];
13940
+ if (isFromRef(value)) {
13941
+ const ref = value[FROM_REF];
13942
+ if (ref.current != null) resolved[prop] = ref.current;
13943
+ }
13944
+ }
13945
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14334
13946
  },
14335
13947
  getPublicInstance: (instance) => instance?.object,
14336
13948
  prepareForCommit: () => null,
@@ -14551,6 +14163,9 @@ function createRoot(canvas) {
14551
14163
  let resolve;
14552
14164
  pending = new Promise((_resolve) => resolve = _resolve);
14553
14165
  const {
14166
+ id: canvasId,
14167
+ primaryCanvas,
14168
+ scheduler: schedulerConfig,
14554
14169
  gl: glConfig,
14555
14170
  renderer: rendererConfig,
14556
14171
  size: propsSize,
@@ -14558,10 +14173,6 @@ function createRoot(canvas) {
14558
14173
  events,
14559
14174
  onCreated: onCreatedCallback,
14560
14175
  shadows = false,
14561
- linear = false,
14562
- flat = false,
14563
- textureColorSpace = three.SRGBColorSpace,
14564
- legacy = false,
14565
14176
  orthographic = false,
14566
14177
  frameloop = "always",
14567
14178
  dpr = [1, 2],
@@ -14573,8 +14184,10 @@ function createRoot(canvas) {
14573
14184
  onDropMissed,
14574
14185
  autoUpdateFrustum = true,
14575
14186
  occlusion = false,
14576
- _sizeProps
14187
+ _sizeProps,
14188
+ forceEven
14577
14189
  } = props;
14190
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || three.SRGBColorSpace;
14578
14191
  const state = store.getState();
14579
14192
  const defaultGLProps = {
14580
14193
  canvas,
@@ -14587,15 +14200,25 @@ function createRoot(canvas) {
14587
14200
  "WebGPURenderer (renderer prop) is not available in this build. Use @react-three/fiber or @react-three/fiber/webgpu instead."
14588
14201
  );
14589
14202
  }
14590
- (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14203
+ const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14591
14204
  if (glConfig && rendererConfig) {
14592
14205
  throw new Error("Cannot use both gl and renderer props at the same time");
14593
14206
  }
14594
14207
  let renderer = state.internal.actualRenderer;
14208
+ if (primaryCanvas && !R3F_BUILD_WEBGPU) {
14209
+ throw new Error(
14210
+ "The `primaryCanvas` prop for multi-canvas rendering is only available with WebGPU. Use @react-three/fiber/webgpu instead."
14211
+ );
14212
+ }
14213
+ if (primaryCanvas && wantsGL) {
14214
+ throw new Error(
14215
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
14216
+ );
14217
+ }
14595
14218
  if (!state.internal.actualRenderer) {
14596
14219
  renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
14597
14220
  state.internal.actualRenderer = renderer;
14598
- state.set({ isLegacy: true, gl: renderer, renderer });
14221
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14599
14222
  }
14600
14223
  let raycaster = state.raycaster;
14601
14224
  if (!raycaster) state.set({ raycaster: raycaster = new three.Raycaster() });
@@ -14604,6 +14227,7 @@ function createRoot(canvas) {
14604
14227
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14605
14228
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14606
14229
  }
14230
+ let tempCamera = state.camera;
14607
14231
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14608
14232
  lastCamera = cameraOptions;
14609
14233
  const isCamera = cameraOptions?.isCamera;
@@ -14623,6 +14247,7 @@ function createRoot(canvas) {
14623
14247
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14624
14248
  }
14625
14249
  state.set({ camera });
14250
+ tempCamera = camera;
14626
14251
  raycaster.camera = camera;
14627
14252
  }
14628
14253
  if (!state.scene) {
@@ -14640,7 +14265,7 @@ function createRoot(canvas) {
14640
14265
  rootScene: scene,
14641
14266
  internal: { ...prev.internal, container: scene }
14642
14267
  }));
14643
- const camera = state.camera;
14268
+ const camera = tempCamera;
14644
14269
  if (camera && !camera.parent) scene.add(camera);
14645
14270
  }
14646
14271
  if (events && !state.events.handlers) {
@@ -14657,6 +14282,9 @@ function createRoot(canvas) {
14657
14282
  if (_sizeProps !== void 0) {
14658
14283
  state.set({ _sizeProps });
14659
14284
  }
14285
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
14286
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
14287
+ }
14660
14288
  const size = computeInitialSize(canvas, propsSize);
14661
14289
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14662
14290
  const wasImperative = state._sizeImperative;
@@ -14683,10 +14311,10 @@ function createRoot(canvas) {
14683
14311
  lastConfiguredProps.performance = performance;
14684
14312
  }
14685
14313
  if (!state.xr) {
14686
- const handleXRFrame = (timestamp, frame) => {
14314
+ const handleXRFrame = (timestamp, _frame) => {
14687
14315
  const state2 = store.getState();
14688
14316
  if (state2.frameloop === "never") return;
14689
- advance(timestamp, true);
14317
+ advance(timestamp);
14690
14318
  };
14691
14319
  const actualRenderer = state.internal.actualRenderer;
14692
14320
  const handleSessionChange = () => {
@@ -14698,16 +14326,16 @@ function createRoot(canvas) {
14698
14326
  };
14699
14327
  const xr = {
14700
14328
  connect() {
14701
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14702
- const actualRenderer2 = renderer2 || gl;
14703
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14704
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
14329
+ const { gl, renderer: renderer2 } = store.getState();
14330
+ const xrManager = (renderer2 || gl).xr;
14331
+ xrManager.addEventListener("sessionstart", handleSessionChange);
14332
+ xrManager.addEventListener("sessionend", handleSessionChange);
14705
14333
  },
14706
14334
  disconnect() {
14707
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14708
- const actualRenderer2 = renderer2 || gl;
14709
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14710
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
14335
+ const { gl, renderer: renderer2 } = store.getState();
14336
+ const xrManager = (renderer2 || gl).xr;
14337
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
14338
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14711
14339
  }
14712
14340
  };
14713
14341
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14719,15 +14347,22 @@ function createRoot(canvas) {
14719
14347
  const oldType = renderer.shadowMap.type;
14720
14348
  renderer.shadowMap.enabled = !!shadows;
14721
14349
  if (is.boo(shadows)) {
14722
- renderer.shadowMap.type = three.PCFSoftShadowMap;
14350
+ renderer.shadowMap.type = three.PCFShadowMap;
14723
14351
  } else if (is.str(shadows)) {
14352
+ if (shadows === "soft") {
14353
+ notifyDepreciated({
14354
+ heading: 'shadows="soft" is deprecated',
14355
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
14356
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
14357
+ });
14358
+ }
14724
14359
  const types = {
14725
14360
  basic: three.BasicShadowMap,
14726
14361
  percentage: three.PCFShadowMap,
14727
- soft: three.PCFSoftShadowMap,
14362
+ soft: three.PCFShadowMap,
14728
14363
  variance: three.VSMShadowMap
14729
14364
  };
14730
- renderer.shadowMap.type = types[shadows] ?? three.PCFSoftShadowMap;
14365
+ renderer.shadowMap.type = types[shadows] ?? three.PCFShadowMap;
14731
14366
  } else if (is.obj(shadows)) {
14732
14367
  Object.assign(renderer.shadowMap, shadows);
14733
14368
  }
@@ -14735,48 +14370,70 @@ function createRoot(canvas) {
14735
14370
  renderer.shadowMap.needsUpdate = true;
14736
14371
  }
14737
14372
  }
14738
- {
14739
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14740
- const linearChanged = linear !== lastConfiguredProps.linear;
14741
- const flatChanged = flat !== lastConfiguredProps.flat;
14742
- if (legacyChanged) {
14743
- three.ColorManagement.enabled = !legacy;
14744
- lastConfiguredProps.legacy = legacy;
14745
- }
14746
- if (!configured || linearChanged) {
14747
- renderer.outputColorSpace = linear ? three.LinearSRGBColorSpace : three.SRGBColorSpace;
14748
- lastConfiguredProps.linear = linear;
14749
- }
14750
- if (!configured || flatChanged) {
14751
- renderer.toneMapping = flat ? three.NoToneMapping : three.ACESFilmicToneMapping;
14752
- lastConfiguredProps.flat = flat;
14753
- }
14754
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14755
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14756
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
14373
+ if (!configured) {
14374
+ renderer.outputColorSpace = three.SRGBColorSpace;
14375
+ renderer.toneMapping = three.ACESFilmicToneMapping;
14757
14376
  }
14758
14377
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14759
14378
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14760
14379
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14761
14380
  }
14381
+ const r3fProps = ["textureColorSpace"];
14382
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
14383
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14762
14384
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14763
- applyProps(renderer, glConfig);
14385
+ const glProps = {};
14386
+ for (const key in glConfig) {
14387
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
14388
+ }
14389
+ applyProps(renderer, glProps);
14764
14390
  }
14765
14391
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14766
14392
  const currentRenderer = state.renderer;
14767
14393
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14768
- applyProps(currentRenderer, rendererConfig);
14394
+ const rendererProps = {};
14395
+ for (const key in rendererConfig) {
14396
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
14397
+ }
14398
+ applyProps(currentRenderer, rendererProps);
14769
14399
  }
14770
14400
  }
14771
- const scheduler = getScheduler();
14401
+ const scheduler$1 = scheduler.getScheduler();
14772
14402
  const rootId = state.internal.rootId;
14773
14403
  if (!rootId) {
14774
- const newRootId = scheduler.generateRootId();
14775
- const unregisterRoot = scheduler.registerRoot(newRootId, {
14404
+ const newRootId = canvasId || scheduler$1.generateRootId();
14405
+ const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14776
14406
  getState: () => store.getState(),
14777
14407
  onError: (err) => store.getState().setError(err)
14778
14408
  });
14779
- const unregisterFrustum = scheduler.register(
14409
+ const unregisterCanvasTarget = scheduler$1.register(
14410
+ () => {
14411
+ const state2 = store.getState();
14412
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14413
+ const renderer2 = state2.internal.actualRenderer;
14414
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
14415
+ }
14416
+ },
14417
+ {
14418
+ id: `${newRootId}_canvasTarget`,
14419
+ rootId: newRootId,
14420
+ phase: "start",
14421
+ system: true
14422
+ }
14423
+ );
14424
+ const unregisterEventsFlush = scheduler$1.register(
14425
+ () => {
14426
+ const state2 = store.getState();
14427
+ state2.events.flush?.();
14428
+ },
14429
+ {
14430
+ id: `${newRootId}_events`,
14431
+ rootId: newRootId,
14432
+ phase: "input",
14433
+ system: true
14434
+ }
14435
+ );
14436
+ const unregisterFrustum = scheduler$1.register(
14780
14437
  () => {
14781
14438
  const state2 = store.getState();
14782
14439
  if (state2.autoUpdateFrustum && state2.camera) {
@@ -14786,11 +14443,11 @@ function createRoot(canvas) {
14786
14443
  {
14787
14444
  id: `${newRootId}_frustum`,
14788
14445
  rootId: newRootId,
14789
- phase: "preRender",
14446
+ before: "render",
14790
14447
  system: true
14791
14448
  }
14792
14449
  );
14793
- const unregisterVisibility = scheduler.register(
14450
+ const unregisterVisibility = scheduler$1.register(
14794
14451
  () => {
14795
14452
  const state2 = store.getState();
14796
14453
  checkVisibility(state2);
@@ -14798,30 +14455,34 @@ function createRoot(canvas) {
14798
14455
  {
14799
14456
  id: `${newRootId}_visibility`,
14800
14457
  rootId: newRootId,
14801
- phase: "preRender",
14458
+ before: "render",
14802
14459
  system: true,
14803
14460
  after: `${newRootId}_frustum`
14804
14461
  }
14805
14462
  );
14806
- const unregisterRender = scheduler.register(
14463
+ const unregisterRender = scheduler$1.register(
14807
14464
  () => {
14808
14465
  const state2 = store.getState();
14809
14466
  const renderer2 = state2.internal.actualRenderer;
14810
- const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14467
+ const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14811
14468
  if (userHandlesRender || state2.internal.priority) return;
14812
14469
  try {
14813
- if (state2.postProcessing?.render) state2.postProcessing.render();
14470
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14814
14471
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14815
14472
  } catch (error) {
14816
14473
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14817
14474
  }
14818
14475
  },
14819
14476
  {
14820
- id: `${newRootId}_render`,
14477
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
14478
+ id: canvasId || `${newRootId}_render`,
14821
14479
  rootId: newRootId,
14822
14480
  phase: "render",
14823
- system: true
14481
+ system: true,
14824
14482
  // Internal flag: this is a system job, not user-controlled
14483
+ // Apply scheduler config for render ordering and rate limiting
14484
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
14485
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14825
14486
  }
14826
14487
  );
14827
14488
  state.set((state2) => ({
@@ -14830,15 +14491,17 @@ function createRoot(canvas) {
14830
14491
  rootId: newRootId,
14831
14492
  unregisterRoot: () => {
14832
14493
  unregisterRoot();
14494
+ unregisterCanvasTarget();
14495
+ unregisterEventsFlush();
14833
14496
  unregisterFrustum();
14834
14497
  unregisterVisibility();
14835
14498
  unregisterRender();
14836
14499
  },
14837
- scheduler
14500
+ scheduler: scheduler$1
14838
14501
  }
14839
14502
  }));
14840
14503
  }
14841
- scheduler.frameloop = frameloop;
14504
+ scheduler$1.frameloop = frameloop;
14842
14505
  onCreated = onCreatedCallback;
14843
14506
  configured = true;
14844
14507
  resolve();
@@ -14888,15 +14551,24 @@ function unmountComponentAtNode(canvas, callback) {
14888
14551
  const renderer = state.internal.actualRenderer;
14889
14552
  const unregisterRoot = state.internal.unregisterRoot;
14890
14553
  if (unregisterRoot) unregisterRoot();
14554
+ const unregisterPrimary = state.internal.unregisterPrimary;
14555
+ if (unregisterPrimary) unregisterPrimary();
14556
+ const canvasTarget = state.internal.canvasTarget;
14557
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14891
14558
  state.events.disconnect?.();
14892
14559
  cleanupHelperGroup(root.store);
14893
- renderer?.renderLists?.dispose?.();
14894
- renderer?.forceContextLoss?.();
14895
- if (renderer?.xr) state.xr.disconnect();
14560
+ if (state.isLegacy && renderer) {
14561
+ ;
14562
+ renderer.renderLists?.dispose?.();
14563
+ renderer.forceContextLoss?.();
14564
+ }
14565
+ if (!state.internal.isSecondary) {
14566
+ if (renderer?.xr) state.xr.disconnect();
14567
+ }
14896
14568
  dispose(state.scene);
14897
14569
  _roots.delete(canvas);
14898
14570
  if (callback) callback(canvas);
14899
- } catch (e) {
14571
+ } catch {
14900
14572
  }
14901
14573
  }, 500);
14902
14574
  }
@@ -14904,36 +14576,34 @@ function unmountComponentAtNode(canvas, callback) {
14904
14576
  }
14905
14577
  }
14906
14578
  function createPortal(children, container, state) {
14907
- return /* @__PURE__ */ jsxRuntime.jsx(PortalWrapper, { children, container, state });
14579
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
14908
14580
  }
14909
- function PortalWrapper({ children, container, state }) {
14581
+ function Portal({ children, container, state }) {
14910
14582
  const isRef = React.useCallback((obj) => obj && "current" in obj, []);
14911
- const [resolvedContainer, setResolvedContainer] = React.useState(() => {
14583
+ const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
14912
14584
  if (isRef(container)) return container.current ?? null;
14913
14585
  return container;
14914
14586
  });
14587
+ const setResolvedContainer = React.useCallback(
14588
+ (newContainer) => {
14589
+ if (!newContainer || newContainer === resolvedContainer) return;
14590
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
14591
+ },
14592
+ [resolvedContainer, _setResolvedContainer, isRef]
14593
+ );
14915
14594
  React.useMemo(() => {
14916
- if (isRef(container)) {
14917
- const current = container.current;
14918
- if (!current) {
14919
- queueMicrotask(() => {
14920
- const updated = container.current;
14921
- if (updated && updated !== resolvedContainer) {
14922
- setResolvedContainer(updated);
14923
- }
14924
- });
14925
- } else if (current !== resolvedContainer) {
14926
- setResolvedContainer(current);
14927
- }
14928
- } else if (container !== resolvedContainer) {
14929
- setResolvedContainer(container);
14595
+ if (isRef(container) && !container.current) {
14596
+ return queueMicrotask(() => {
14597
+ setResolvedContainer(container.current);
14598
+ });
14930
14599
  }
14931
- }, [container, resolvedContainer, isRef]);
14600
+ setResolvedContainer(container);
14601
+ }, [container, isRef, setResolvedContainer]);
14932
14602
  if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
14933
14603
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14934
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
14604
+ return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14935
14605
  }
14936
- function Portal({ state = {}, children, container }) {
14606
+ function PortalInner({ state = {}, children, container }) {
14937
14607
  const { events, size, injectScene = true, ...rest } = state;
14938
14608
  const previousRoot = useStore();
14939
14609
  const [raycaster] = React.useState(() => new three.Raycaster());
@@ -14954,11 +14624,12 @@ function Portal({ state = {}, children, container }) {
14954
14624
  };
14955
14625
  }, [portalScene, container, injectScene]);
14956
14626
  const inject = useMutableCallback((rootState, injectState) => {
14627
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14957
14628
  let viewport = void 0;
14958
- if (injectState.camera && size) {
14629
+ if (injectState.camera && (size || injectState.size)) {
14959
14630
  const camera = injectState.camera;
14960
- viewport = rootState.viewport.getCurrentViewport(camera, new three.Vector3(), size);
14961
- if (camera !== rootState.camera) updateCamera(camera, size);
14631
+ viewport = rootState.viewport.getCurrentViewport(camera, new three.Vector3(), resolvedSize);
14632
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14962
14633
  }
14963
14634
  return {
14964
14635
  // The intersect consists of the previous root state
@@ -14975,7 +14646,7 @@ function Portal({ state = {}, children, container }) {
14975
14646
  previousRoot,
14976
14647
  // Events, size and viewport can be overridden by the inject layer
14977
14648
  events: { ...rootState.events, ...injectState.events, ...events },
14978
- size: { ...rootState.size, ...size },
14649
+ size: resolvedSize,
14979
14650
  viewport: { ...rootState.viewport, ...viewport },
14980
14651
  // Layers are allowed to override events
14981
14652
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14987,9 +14658,13 @@ function Portal({ state = {}, children, container }) {
14987
14658
  const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
14988
14659
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
14989
14660
  onMutate(previousRoot.getState());
14990
- previousRoot.subscribe(onMutate);
14991
14661
  return store;
14992
14662
  }, [previousRoot, container]);
14663
+ useIsomorphicLayoutEffect(() => {
14664
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
14665
+ const unsubscribe = previousRoot.subscribe(onMutate);
14666
+ return unsubscribe;
14667
+ }, [previousRoot, usePortalStore]);
14993
14668
  return (
14994
14669
  // @ts-ignore, reconciler types are not maintained
14995
14670
  /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
@@ -15003,21 +14678,59 @@ function flushSync(fn) {
15003
14678
  return reconciler.flushSyncFromReconciler(fn);
15004
14679
  }
15005
14680
 
14681
+ function parseBackground(background) {
14682
+ if (!background) return null;
14683
+ if (typeof background === "object" && !background.isColor) {
14684
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
14685
+ return {
14686
+ ...rest,
14687
+ preset,
14688
+ files: envMap || files,
14689
+ backgroundFiles: backgroundMap,
14690
+ background: true
14691
+ };
14692
+ }
14693
+ if (typeof background === "number") {
14694
+ return { color: background, background: true };
14695
+ }
14696
+ if (typeof background === "string") {
14697
+ if (background in presetsObj) {
14698
+ return { preset: background, background: true };
14699
+ }
14700
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
14701
+ return { files: background, background: true };
14702
+ }
14703
+ return { color: background, background: true };
14704
+ }
14705
+ if (background.isColor) {
14706
+ return { color: background, background: true };
14707
+ }
14708
+ return null;
14709
+ }
14710
+
14711
+ function clearHmrCaches(store) {
14712
+ store.setState((state) => ({
14713
+ nodes: {},
14714
+ uniforms: {},
14715
+ buffers: {},
14716
+ gpuStorage: {},
14717
+ _hmrVersion: state._hmrVersion + 1
14718
+ }));
14719
+ }
14720
+
15006
14721
  function CanvasImpl({
15007
14722
  ref,
15008
14723
  children,
15009
14724
  fallback,
15010
14725
  resize,
15011
14726
  style,
14727
+ id,
15012
14728
  gl,
15013
- renderer,
14729
+ renderer: rendererProp,
15014
14730
  events = createPointerEvents,
15015
14731
  eventSource,
15016
14732
  eventPrefix,
15017
14733
  shadows,
15018
- linear,
15019
- flat,
15020
- legacy,
15021
14734
  orthographic,
15022
14735
  frameloop,
15023
14736
  dpr,
@@ -15025,6 +14738,8 @@ function CanvasImpl({
15025
14738
  raycaster,
15026
14739
  camera,
15027
14740
  scene,
14741
+ autoUpdateFrustum,
14742
+ occlusion,
15028
14743
  onPointerMissed,
15029
14744
  onDragOverMissed,
15030
14745
  onDropMissed,
@@ -15032,10 +14747,25 @@ function CanvasImpl({
15032
14747
  hmr,
15033
14748
  width,
15034
14749
  height,
14750
+ background,
14751
+ forceEven,
15035
14752
  ...props
15036
14753
  }) {
14754
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14755
+ let primaryCanvas;
14756
+ let scheduler;
14757
+ let renderer;
14758
+ if (isRendererConfig) {
14759
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14760
+ primaryCanvas = pc;
14761
+ scheduler = sc;
14762
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14763
+ } else {
14764
+ renderer = rendererProp;
14765
+ }
15037
14766
  React__namespace.useMemo(() => extend(THREE), []);
15038
14767
  const Bridge = useBridge();
14768
+ const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15039
14769
  const hasInitialSizeRef = React__namespace.useRef(false);
15040
14770
  const measureConfig = React__namespace.useMemo(() => {
15041
14771
  if (!hasInitialSizeRef.current) {
@@ -15052,15 +14782,20 @@ function CanvasImpl({
15052
14782
  };
15053
14783
  }, [resize, hasInitialSizeRef.current]);
15054
14784
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15055
- const effectiveSize = React__namespace.useMemo(
15056
- () => ({
15057
- width: width ?? containerRect.width,
15058
- height: height ?? containerRect.height,
14785
+ const effectiveSize = React__namespace.useMemo(() => {
14786
+ let w = width ?? containerRect.width;
14787
+ let h = height ?? containerRect.height;
14788
+ if (forceEven) {
14789
+ w = Math.ceil(w / 2) * 2;
14790
+ h = Math.ceil(h / 2) * 2;
14791
+ }
14792
+ return {
14793
+ width: w,
14794
+ height: h,
15059
14795
  top: containerRect.top,
15060
14796
  left: containerRect.left
15061
- }),
15062
- [width, height, containerRect]
15063
- );
14797
+ };
14798
+ }, [width, height, containerRect, forceEven]);
15064
14799
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15065
14800
  hasInitialSizeRef.current = true;
15066
14801
  }
@@ -15100,23 +14835,26 @@ function CanvasImpl({
15100
14835
  async function run() {
15101
14836
  if (!effectActiveRef.current || !root.current) return;
15102
14837
  await root.current.configure({
14838
+ id,
14839
+ primaryCanvas,
14840
+ scheduler,
15103
14841
  gl,
15104
14842
  renderer,
15105
14843
  scene,
15106
14844
  events,
15107
14845
  shadows,
15108
- linear,
15109
- flat,
15110
- legacy,
15111
14846
  orthographic,
15112
14847
  frameloop,
15113
14848
  dpr,
15114
14849
  performance,
15115
14850
  raycaster,
15116
14851
  camera,
14852
+ autoUpdateFrustum,
14853
+ occlusion,
15117
14854
  size: effectiveSize,
15118
14855
  // Store size props for reset functionality
15119
14856
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
14857
+ forceEven,
15120
14858
  // Pass mutable reference to onPointerMissed so it's free to update
15121
14859
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15122
14860
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15140,7 +14878,10 @@ function CanvasImpl({
15140
14878
  });
15141
14879
  if (!effectActiveRef.current || !root.current) return;
15142
14880
  root.current.render(
15143
- /* @__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 }) }) })
14881
+ /* @__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: [
14882
+ backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
14883
+ children ?? null
14884
+ ] }) }) })
15144
14885
  );
15145
14886
  }
15146
14887
  run();
@@ -15167,20 +14908,15 @@ function CanvasImpl({
15167
14908
  const canvas = canvasRef.current;
15168
14909
  if (!canvas) return;
15169
14910
  const handleHMR = () => {
15170
- const rootEntry = _roots.get(canvas);
15171
- if (rootEntry?.store) {
15172
- rootEntry.store.setState((state) => ({
15173
- nodes: {},
15174
- uniforms: {},
15175
- _hmrVersion: state._hmrVersion + 1
15176
- }));
15177
- }
14911
+ queueMicrotask(() => {
14912
+ const rootEntry = _roots.get(canvas);
14913
+ if (rootEntry?.store) clearHmrCaches(rootEntry.store);
14914
+ });
15178
14915
  };
15179
14916
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('legacy.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15180
14917
  const hot = undefined;
15181
14918
  hot.on("vite:afterUpdate", handleHMR);
15182
- return () => hot.dispose?.(() => {
15183
- });
14919
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15184
14920
  }
15185
14921
  if (typeof module !== "undefined" && module.hot) {
15186
14922
  const hot = module.hot;
@@ -15203,7 +14939,16 @@ function CanvasImpl({
15203
14939
  ...style
15204
14940
  },
15205
14941
  ...props,
15206
- 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 }) })
14942
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
14943
+ "canvas",
14944
+ {
14945
+ ref: canvasRef,
14946
+ id,
14947
+ className: "r3f-canvas",
14948
+ style: { display: "block", width: "100%", height: "100%" },
14949
+ children: fallback
14950
+ }
14951
+ ) })
15207
14952
  }
15208
14953
  );
15209
14954
  }
@@ -15213,15 +14958,23 @@ function Canvas(props) {
15213
14958
 
15214
14959
  extend(THREE);
15215
14960
 
14961
+ exports.Scheduler = scheduler.Scheduler;
14962
+ exports.getScheduler = scheduler.getScheduler;
15216
14963
  exports.Block = Block;
15217
14964
  exports.Canvas = Canvas;
14965
+ exports.Environment = Environment;
14966
+ exports.EnvironmentCube = EnvironmentCube;
14967
+ exports.EnvironmentMap = EnvironmentMap;
14968
+ exports.EnvironmentPortal = EnvironmentPortal;
15218
14969
  exports.ErrorBoundary = ErrorBoundary;
14970
+ exports.FROM_REF = FROM_REF;
15219
14971
  exports.IsObject = IsObject;
14972
+ exports.ONCE = ONCE;
14973
+ exports.Portal = Portal;
15220
14974
  exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
15221
14975
  exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
15222
14976
  exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
15223
14977
  exports.RESERVED_PROPS = RESERVED_PROPS;
15224
- exports.Scheduler = Scheduler;
15225
14978
  exports.Texture = Texture;
15226
14979
  exports._roots = _roots;
15227
14980
  exports.act = act;
@@ -15246,30 +14999,40 @@ exports.events = createPointerEvents;
15246
14999
  exports.extend = extend;
15247
15000
  exports.findInitialRoot = findInitialRoot;
15248
15001
  exports.flushSync = flushSync;
15002
+ exports.fromRef = fromRef;
15249
15003
  exports.getInstanceProps = getInstanceProps;
15004
+ exports.getPrimary = getPrimary;
15005
+ exports.getPrimaryIds = getPrimaryIds;
15250
15006
  exports.getRootState = getRootState;
15251
- exports.getScheduler = getScheduler;
15252
15007
  exports.getUuidPrefix = getUuidPrefix;
15253
15008
  exports.hasConstructor = hasConstructor;
15009
+ exports.hasPrimary = hasPrimary;
15254
15010
  exports.invalidate = invalidate;
15255
15011
  exports.invalidateInstance = invalidateInstance;
15256
15012
  exports.is = is;
15257
15013
  exports.isColorRepresentation = isColorRepresentation;
15258
15014
  exports.isCopyable = isCopyable;
15015
+ exports.isFromRef = isFromRef;
15259
15016
  exports.isObject3D = isObject3D;
15017
+ exports.isOnce = isOnce;
15260
15018
  exports.isOrthographicCamera = isOrthographicCamera;
15261
15019
  exports.isRef = isRef;
15262
15020
  exports.isRenderer = isRenderer;
15263
15021
  exports.isTexture = isTexture;
15264
15022
  exports.isVectorLike = isVectorLike;
15023
+ exports.once = once;
15265
15024
  exports.prepare = prepare;
15025
+ exports.presetsObj = presetsObj;
15266
15026
  exports.reconciler = reconciler;
15027
+ exports.registerPrimary = registerPrimary;
15267
15028
  exports.removeInteractivity = removeInteractivity;
15268
15029
  exports.resolve = resolve;
15269
15030
  exports.unmountComponentAtNode = unmountComponentAtNode;
15031
+ exports.unregisterPrimary = unregisterPrimary;
15270
15032
  exports.updateCamera = updateCamera;
15271
15033
  exports.updateFrustum = updateFrustum;
15272
15034
  exports.useBridge = useBridge;
15035
+ exports.useEnvironment = useEnvironment;
15273
15036
  exports.useFrame = useFrame;
15274
15037
  exports.useGraph = useGraph;
15275
15038
  exports.useInstanceHandle = useInstanceHandle;
@@ -15281,3 +15044,4 @@ exports.useStore = useStore;
15281
15044
  exports.useTexture = useTexture;
15282
15045
  exports.useTextures = useTextures;
15283
15046
  exports.useThree = useThree;
15047
+ exports.waitForPrimary = waitForPrimary;