@react-three/fiber 10.0.0-alpha.1 → 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.
@@ -1,13 +1,21 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
2
+ import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, Vector4, PostProcessing } from 'three/webgpu';
3
3
  import { Inspector } from 'three/addons/inspector/Inspector.js';
4
- import { jsx, Fragment } from 'react/jsx-runtime';
4
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
  import * as React from 'react';
6
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
6
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
7
7
  import useMeasure from 'react-use-measure';
8
8
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
9
+ import { useThree as useThree$1, useLoader as useLoader$1, useFrame as useFrame$1, createPortal as createPortal$1, applyProps as applyProps$1, extend as extend$1 } from '@react-three/fiber';
10
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
11
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
12
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
13
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
14
+ import { GainMapLoader } from '@monogrid/gainmap-js';
9
15
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
10
16
  import { createWithEqualityFn } from 'zustand/traditional';
17
+ import { getScheduler } from '@pmndrs/scheduler';
18
+ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
11
19
  import { suspend, preload, clear } from 'suspend-react';
12
20
  import { dequal } from 'dequal/lite';
13
21
  import 'zustand/shallow';
@@ -46,9 +54,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
46
54
  WebGLRenderer: WebGLRenderer
47
55
  }, [webgpu]);
48
56
 
49
- var __defProp$2 = Object.defineProperty;
50
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
51
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
57
+ const primaryRegistry = /* @__PURE__ */ new Map();
58
+ const pendingSubscribers = /* @__PURE__ */ new Map();
59
+ function registerPrimary(id, renderer, store) {
60
+ if (primaryRegistry.has(id)) {
61
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
62
+ }
63
+ const entry = { renderer, store };
64
+ primaryRegistry.set(id, entry);
65
+ const subscribers = pendingSubscribers.get(id);
66
+ if (subscribers) {
67
+ subscribers.forEach((callback) => callback(entry));
68
+ pendingSubscribers.delete(id);
69
+ }
70
+ return () => {
71
+ const currentEntry = primaryRegistry.get(id);
72
+ if (currentEntry?.renderer === renderer) {
73
+ primaryRegistry.delete(id);
74
+ }
75
+ };
76
+ }
77
+ function getPrimary(id) {
78
+ return primaryRegistry.get(id);
79
+ }
80
+ function waitForPrimary(id, timeout = 5e3) {
81
+ const existing = primaryRegistry.get(id);
82
+ if (existing) {
83
+ return Promise.resolve(existing);
84
+ }
85
+ return new Promise((resolve, reject) => {
86
+ const timeoutId = setTimeout(() => {
87
+ const subscribers = pendingSubscribers.get(id);
88
+ if (subscribers) {
89
+ const index = subscribers.indexOf(callback);
90
+ if (index !== -1) subscribers.splice(index, 1);
91
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
92
+ }
93
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
94
+ }, timeout);
95
+ const callback = (entry) => {
96
+ clearTimeout(timeoutId);
97
+ resolve(entry);
98
+ };
99
+ if (!pendingSubscribers.has(id)) {
100
+ pendingSubscribers.set(id, []);
101
+ }
102
+ pendingSubscribers.get(id).push(callback);
103
+ });
104
+ }
105
+ function hasPrimary(id) {
106
+ return primaryRegistry.has(id);
107
+ }
108
+ function unregisterPrimary(id) {
109
+ primaryRegistry.delete(id);
110
+ }
111
+ function getPrimaryIds() {
112
+ return Array.from(primaryRegistry.keys());
113
+ }
114
+
115
+ const presetsObj = {
116
+ apartment: "lebombo_1k.hdr",
117
+ city: "potsdamer_platz_1k.hdr",
118
+ dawn: "kiara_1_dawn_1k.hdr",
119
+ forest: "forest_slope_1k.hdr",
120
+ lobby: "st_fagans_interior_1k.hdr",
121
+ night: "dikhololo_night_1k.hdr",
122
+ park: "rooitou_park_1k.hdr",
123
+ studio: "studio_small_03_1k.hdr",
124
+ sunset: "venice_sunset_1k.hdr",
125
+ warehouse: "empty_warehouse_01_1k.hdr"
126
+ };
127
+
128
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
129
+ const isArray = (arr) => Array.isArray(arr);
130
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
131
+ function useEnvironment({
132
+ files = defaultFiles,
133
+ path = "",
134
+ preset = void 0,
135
+ colorSpace = void 0,
136
+ extensions
137
+ } = {}) {
138
+ if (preset) {
139
+ validatePreset(preset);
140
+ files = presetsObj[preset];
141
+ path = CUBEMAP_ROOT;
142
+ }
143
+ const multiFile = isArray(files);
144
+ const { extension, isCubemap } = getExtension(files);
145
+ const loader = getLoader$1(extension);
146
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
147
+ const renderer = useThree$1((state) => state.renderer);
148
+ useLayoutEffect(() => {
149
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
150
+ function clearGainmapTexture() {
151
+ useLoader$1.clear(loader, multiFile ? [files] : files);
152
+ }
153
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
154
+ }, [extension, files, loader, multiFile, renderer.domElement]);
155
+ const loaderResult = useLoader$1(
156
+ loader,
157
+ multiFile ? [files] : files,
158
+ (loader2) => {
159
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
160
+ loader2.setRenderer?.(renderer);
161
+ }
162
+ loader2.setPath?.(path);
163
+ if (extensions) extensions(loader2);
164
+ }
165
+ );
166
+ let texture = multiFile ? (
167
+ // @ts-ignore
168
+ loaderResult[0]
169
+ ) : loaderResult;
170
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
171
+ texture = texture.renderTarget?.texture;
172
+ }
173
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
174
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
175
+ return texture;
176
+ }
177
+ const preloadDefaultOptions = {
178
+ files: defaultFiles,
179
+ path: "",
180
+ preset: void 0,
181
+ extensions: void 0
182
+ };
183
+ useEnvironment.preload = (preloadOptions) => {
184
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
185
+ let { files, path = "" } = options;
186
+ const { preset, extensions } = options;
187
+ if (preset) {
188
+ validatePreset(preset);
189
+ files = presetsObj[preset];
190
+ path = CUBEMAP_ROOT;
191
+ }
192
+ const { extension } = getExtension(files);
193
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
194
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
195
+ }
196
+ const loader = getLoader$1(extension);
197
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
198
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
199
+ loader2.setPath?.(path);
200
+ if (extensions) extensions(loader2);
201
+ });
202
+ };
203
+ const clearDefaultOptins = {
204
+ files: defaultFiles,
205
+ preset: void 0
206
+ };
207
+ useEnvironment.clear = (clearOptions) => {
208
+ const options = { ...clearDefaultOptins, ...clearOptions };
209
+ let { files } = options;
210
+ const { preset } = options;
211
+ if (preset) {
212
+ validatePreset(preset);
213
+ files = presetsObj[preset];
214
+ }
215
+ const { extension } = getExtension(files);
216
+ const loader = getLoader$1(extension);
217
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
218
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
219
+ };
220
+ function validatePreset(preset) {
221
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
222
+ }
223
+ function getExtension(files) {
224
+ const isCubemap = isArray(files) && files.length === 6;
225
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
226
+ const firstEntry = isArray(files) ? files[0] : files;
227
+ 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();
228
+ return { extension, isCubemap, isGainmap };
229
+ }
230
+ function getLoader$1(extension) {
231
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
232
+ return loader;
233
+ }
234
+
235
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
236
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
237
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
238
+ sceneProps = {
239
+ backgroundBlurriness: 0,
240
+ backgroundIntensity: 1,
241
+ backgroundRotation: [0, 0, 0],
242
+ environmentIntensity: 1,
243
+ environmentRotation: [0, 0, 0],
244
+ ...sceneProps
245
+ };
246
+ const target = resolveScene(scene || defaultScene);
247
+ const oldbg = target.background;
248
+ const oldenv = target.environment;
249
+ const oldSceneProps = {
250
+ // @ts-ignore
251
+ backgroundBlurriness: target.backgroundBlurriness,
252
+ // @ts-ignore
253
+ backgroundIntensity: target.backgroundIntensity,
254
+ // @ts-ignore
255
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
256
+ // @ts-ignore
257
+ environmentIntensity: target.environmentIntensity,
258
+ // @ts-ignore
259
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
260
+ };
261
+ if (background !== "only") target.environment = texture;
262
+ if (background) target.background = texture;
263
+ applyProps$1(target, sceneProps);
264
+ return () => {
265
+ if (background !== "only") target.environment = oldenv;
266
+ if (background) target.background = oldbg;
267
+ applyProps$1(target, oldSceneProps);
268
+ };
269
+ }
270
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
271
+ const defaultScene = useThree$1((state) => state.scene);
272
+ React.useLayoutEffect(() => {
273
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
274
+ });
275
+ return null;
276
+ }
277
+ function EnvironmentCube({
278
+ background = false,
279
+ scene,
280
+ blur,
281
+ backgroundBlurriness,
282
+ backgroundIntensity,
283
+ backgroundRotation,
284
+ environmentIntensity,
285
+ environmentRotation,
286
+ ...rest
287
+ }) {
288
+ const texture = useEnvironment(rest);
289
+ const defaultScene = useThree$1((state) => state.scene);
290
+ React.useLayoutEffect(() => {
291
+ return setEnvProps(background, scene, defaultScene, texture, {
292
+ backgroundBlurriness: blur ?? backgroundBlurriness,
293
+ backgroundIntensity,
294
+ backgroundRotation,
295
+ environmentIntensity,
296
+ environmentRotation
297
+ });
298
+ });
299
+ React.useEffect(() => {
300
+ return () => {
301
+ texture.dispose();
302
+ };
303
+ }, [texture]);
304
+ return null;
305
+ }
306
+ function EnvironmentPortal({
307
+ children,
308
+ near = 0.1,
309
+ far = 1e3,
310
+ resolution = 256,
311
+ frames = 1,
312
+ map,
313
+ background = false,
314
+ blur,
315
+ backgroundBlurriness,
316
+ backgroundIntensity,
317
+ backgroundRotation,
318
+ environmentIntensity,
319
+ environmentRotation,
320
+ scene,
321
+ files,
322
+ path,
323
+ preset = void 0,
324
+ extensions
325
+ }) {
326
+ const gl = useThree$1((state) => state.gl);
327
+ const defaultScene = useThree$1((state) => state.scene);
328
+ const camera = React.useRef(null);
329
+ const [virtualScene] = React.useState(() => new Scene());
330
+ const fbo = React.useMemo(() => {
331
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
332
+ fbo2.texture.type = HalfFloatType;
333
+ return fbo2;
334
+ }, [resolution]);
335
+ React.useEffect(() => {
336
+ return () => {
337
+ fbo.dispose();
338
+ };
339
+ }, [fbo]);
340
+ React.useLayoutEffect(() => {
341
+ if (frames === 1) {
342
+ const autoClear = gl.autoClear;
343
+ gl.autoClear = true;
344
+ camera.current.update(gl, virtualScene);
345
+ gl.autoClear = autoClear;
346
+ }
347
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
348
+ backgroundBlurriness: blur ?? backgroundBlurriness,
349
+ backgroundIntensity,
350
+ backgroundRotation,
351
+ environmentIntensity,
352
+ environmentRotation
353
+ });
354
+ }, [
355
+ children,
356
+ virtualScene,
357
+ fbo.texture,
358
+ scene,
359
+ defaultScene,
360
+ background,
361
+ frames,
362
+ gl,
363
+ blur,
364
+ backgroundBlurriness,
365
+ backgroundIntensity,
366
+ backgroundRotation,
367
+ environmentIntensity,
368
+ environmentRotation
369
+ ]);
370
+ let count = 1;
371
+ useFrame$1(() => {
372
+ if (frames === Infinity || count < frames) {
373
+ const autoClear = gl.autoClear;
374
+ gl.autoClear = true;
375
+ camera.current.update(gl, virtualScene);
376
+ gl.autoClear = autoClear;
377
+ count++;
378
+ }
379
+ });
380
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
381
+ /* @__PURE__ */ jsxs(Fragment, { children: [
382
+ children,
383
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
384
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
385
+ ] }),
386
+ virtualScene
387
+ ) });
388
+ }
389
+ function EnvironmentGround(props) {
390
+ const textureDefault = useEnvironment(props);
391
+ const texture = props.map || textureDefault;
392
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
393
+ React.useEffect(() => {
394
+ return () => {
395
+ textureDefault.dispose();
396
+ };
397
+ }, [textureDefault]);
398
+ const height = props.ground?.height ?? 15;
399
+ const radius = props.ground?.radius ?? 60;
400
+ const scale = props.ground?.scale ?? 1e3;
401
+ const args = React.useMemo(
402
+ () => [texture, height, radius],
403
+ [texture, height, radius]
404
+ );
405
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
406
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
407
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
408
+ ] });
409
+ }
410
+ function EnvironmentColor({ color, scene }) {
411
+ const defaultScene = useThree$1((state) => state.scene);
412
+ React.useLayoutEffect(() => {
413
+ if (color === void 0) return;
414
+ const target = resolveScene(scene || defaultScene);
415
+ const oldBg = target.background;
416
+ target.background = new Color(color);
417
+ return () => {
418
+ target.background = oldBg;
419
+ };
420
+ });
421
+ return null;
422
+ }
423
+ function EnvironmentDualSource(props) {
424
+ const { backgroundFiles, ...envProps } = props;
425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
426
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
427
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
428
+ ] });
429
+ }
430
+ function Environment(props) {
431
+ if (props.color && !props.files && !props.preset && !props.map) {
432
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
433
+ }
434
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
435
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
436
+ }
437
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
438
+ }
439
+
440
+ var __defProp$1 = Object.defineProperty;
441
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
442
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
52
443
  const act = React["act"];
53
444
  const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React.useLayoutEffect : React.useEffect;
54
445
  function useMutableCallback(fn) {
@@ -80,7 +471,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
80
471
  return _a = class extends React.Component {
81
472
  constructor() {
82
473
  super(...arguments);
83
- __publicField$2(this, "state", { error: false });
474
+ __publicField$1(this, "state", { error: false });
84
475
  }
85
476
  componentDidCatch(err) {
86
477
  this.props.set(err);
@@ -88,7 +479,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
88
479
  render() {
89
480
  return this.state.error ? null : this.props.children;
90
481
  }
91
- }, __publicField$2(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
482
+ }, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
92
483
  })();
93
484
 
94
485
  const is = {
@@ -225,7 +616,8 @@ function prepare(target, root, type, props) {
225
616
  object,
226
617
  eventCount: 0,
227
618
  handlers: {},
228
- isHidden: false
619
+ isHidden: false,
620
+ deferredRefs: []
229
621
  };
230
622
  if (object) object.__r3f = instance;
231
623
  }
@@ -274,7 +666,7 @@ function createOcclusionObserverNode(store, uniform) {
274
666
  let occlusionSetupPromise = null;
275
667
  function enableOcclusion(store) {
276
668
  const state = store.getState();
277
- const { internal, renderer, rootScene } = state;
669
+ const { internal, renderer } = state;
278
670
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
279
671
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
280
672
  if (!hasOcclusionSupport) {
@@ -437,6 +829,22 @@ function hasVisibilityHandlers(handlers) {
437
829
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
438
830
  }
439
831
 
832
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
833
+ function fromRef(ref) {
834
+ return { [FROM_REF]: ref };
835
+ }
836
+ function isFromRef(value) {
837
+ return value !== null && typeof value === "object" && FROM_REF in value;
838
+ }
839
+
840
+ const ONCE = Symbol.for("@react-three/fiber.once");
841
+ function once(...args) {
842
+ return { [ONCE]: args.length ? args : true };
843
+ }
844
+ function isOnce(value) {
845
+ return value !== null && typeof value === "object" && ONCE in value;
846
+ }
847
+
440
848
  const RESERVED_PROPS = [
441
849
  "children",
442
850
  "key",
@@ -507,7 +915,7 @@ function getMemoizedPrototype(root) {
507
915
  ctor = new root.constructor();
508
916
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
509
917
  }
510
- } catch (e) {
918
+ } catch {
511
919
  }
512
920
  return ctor;
513
921
  }
@@ -538,7 +946,7 @@ function applyProps(object, props) {
538
946
  const rootState = instance && findInitialRoot(instance).getState();
539
947
  const prevHandlers = instance?.eventCount;
540
948
  for (const prop in props) {
541
- let value = props[prop];
949
+ const value = props[prop];
542
950
  if (RESERVED_PROPS.includes(prop)) continue;
543
951
  if (instance && EVENT_REGEX.test(prop)) {
544
952
  if (typeof value === "function") instance.handlers[prop] = value;
@@ -553,6 +961,25 @@ function applyProps(object, props) {
553
961
  continue;
554
962
  }
555
963
  if (value === void 0) continue;
964
+ if (isFromRef(value)) {
965
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
966
+ continue;
967
+ }
968
+ if (isOnce(value)) {
969
+ if (instance?.appliedOnce?.has(prop)) continue;
970
+ if (instance) {
971
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
972
+ instance.appliedOnce.add(prop);
973
+ }
974
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
975
+ const args = value[ONCE];
976
+ if (typeof targetRoot[targetKey] === "function") {
977
+ targetRoot[targetKey](...args === true ? [] : args);
978
+ } else if (args !== true && args.length > 0) {
979
+ targetRoot[targetKey] = args[0];
980
+ }
981
+ continue;
982
+ }
556
983
  let { root, key, target } = resolve(object, prop);
557
984
  if (target === void 0 && (typeof root !== "object" || root === null)) {
558
985
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -575,7 +1002,10 @@ function applyProps(object, props) {
575
1002
  else target.set(value);
576
1003
  } else {
577
1004
  root[key] = value;
578
- 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
1005
+ if (key.endsWith("Node") && root.isMaterial) {
1006
+ root.needsUpdate = true;
1007
+ }
1008
+ if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
579
1009
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
580
1010
  root[key].colorSpace = rootState.textureColorSpace;
581
1011
  }
@@ -608,38 +1038,60 @@ function applyProps(object, props) {
608
1038
  return object;
609
1039
  }
610
1040
 
1041
+ const DEFAULT_POINTER_ID = 0;
1042
+ const XR_POINTER_ID_START = 1e3;
1043
+ function getPointerState(internal, pointerId) {
1044
+ let state = internal.pointerMap.get(pointerId);
1045
+ if (!state) {
1046
+ state = {
1047
+ hovered: /* @__PURE__ */ new Map(),
1048
+ captured: /* @__PURE__ */ new Map(),
1049
+ initialClick: [0, 0],
1050
+ initialHits: []
1051
+ };
1052
+ internal.pointerMap.set(pointerId, state);
1053
+ }
1054
+ return state;
1055
+ }
1056
+ function getPointerId(event) {
1057
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1058
+ }
611
1059
  function makeId(event) {
612
1060
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
613
1061
  }
614
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
615
- const captureData = captures.get(obj);
1062
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1063
+ const pointerState = internal.pointerMap.get(pointerId);
1064
+ if (!pointerState) return;
1065
+ const captureData = pointerState.captured.get(obj);
616
1066
  if (captureData) {
617
- captures.delete(obj);
618
- if (captures.size === 0) {
619
- capturedMap.delete(pointerId);
620
- captureData.target.releasePointerCapture(pointerId);
621
- }
1067
+ pointerState.captured.delete(obj);
1068
+ captureData.target.releasePointerCapture(pointerId);
622
1069
  }
623
1070
  }
624
1071
  function removeInteractivity(store, object) {
625
1072
  const { internal } = store.getState();
626
1073
  internal.interaction = internal.interaction.filter((o) => o !== object);
627
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
628
- internal.hovered.forEach((value, key) => {
629
- if (value.eventObject === object || value.object === object) {
630
- internal.hovered.delete(key);
1074
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1075
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1076
+ pointerState.hovered.forEach((value, key) => {
1077
+ if (value.eventObject === object || value.object === object) {
1078
+ pointerState.hovered.delete(key);
1079
+ }
1080
+ });
1081
+ if (pointerState.captured.has(object)) {
1082
+ releaseInternalPointerCapture(internal, object, pointerId);
631
1083
  }
632
- });
633
- internal.capturedMap.forEach((captures, pointerId) => {
634
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
635
- });
1084
+ }
636
1085
  unregisterVisibility(store, object);
637
1086
  }
638
1087
  function createEvents(store) {
639
- function calculateDistance(event) {
1088
+ function calculateDistance(event, pointerId) {
640
1089
  const { internal } = store.getState();
641
- const dx = event.offsetX - internal.initialClick[0];
642
- const dy = event.offsetY - internal.initialClick[1];
1090
+ const pointerState = internal.pointerMap.get(pointerId);
1091
+ if (!pointerState) return 0;
1092
+ const [initialX, initialY] = pointerState.initialClick;
1093
+ const dx = event.offsetX - initialX;
1094
+ const dy = event.offsetY - initialY;
643
1095
  return Math.round(Math.sqrt(dx * dx + dy * dy));
644
1096
  }
645
1097
  function filterPointerEvents(objects) {
@@ -675,6 +1127,15 @@ function createEvents(store) {
675
1127
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
676
1128
  }
677
1129
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1130
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1131
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1132
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1133
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1134
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1135
+ if (aInteractivePriority !== bInteractivePriority) {
1136
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1137
+ }
1138
+ }
678
1139
  const aState = getRootState(a.object);
679
1140
  const bState = getRootState(b.object);
680
1141
  const aPriority = aState?.events?.priority ?? 1;
@@ -690,14 +1151,19 @@ function createEvents(store) {
690
1151
  for (const hit of hits) {
691
1152
  let eventObject = hit.object;
692
1153
  while (eventObject) {
693
- if (eventObject.__r3f?.eventCount)
1154
+ if (eventObject.__r3f?.eventCount) {
694
1155
  intersections.push({ ...hit, eventObject });
1156
+ }
695
1157
  eventObject = eventObject.parent;
696
1158
  }
697
1159
  }
698
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
699
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
700
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1160
+ if ("pointerId" in event) {
1161
+ const pointerId = event.pointerId;
1162
+ const pointerState = state.internal.pointerMap.get(pointerId);
1163
+ if (pointerState?.captured.size) {
1164
+ for (const captureData of pointerState.captured.values()) {
1165
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1166
+ }
701
1167
  }
702
1168
  }
703
1169
  return intersections;
@@ -710,28 +1176,26 @@ function createEvents(store) {
710
1176
  if (state) {
711
1177
  const { raycaster, pointer, camera, internal } = state;
712
1178
  const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
713
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1179
+ const hasPointerCapture = (id) => {
1180
+ const pointerState = internal.pointerMap.get(id);
1181
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1182
+ };
714
1183
  const setPointerCapture = (id) => {
715
1184
  const captureData = { intersection: hit, target: event.target };
716
- if (internal.capturedMap.has(id)) {
717
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
718
- } else {
719
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
720
- }
1185
+ const pointerState = getPointerState(internal, id);
1186
+ pointerState.captured.set(hit.eventObject, captureData);
721
1187
  event.target.setPointerCapture(id);
722
1188
  };
723
1189
  const releasePointerCapture = (id) => {
724
- const captures = internal.capturedMap.get(id);
725
- if (captures) {
726
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
727
- }
1190
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
728
1191
  };
729
- let extractEventProps = {};
730
- for (let prop in event) {
731
- let property = event[prop];
1192
+ const extractEventProps = {};
1193
+ for (const prop in event) {
1194
+ const property = event[prop];
732
1195
  if (typeof property !== "function") extractEventProps[prop] = property;
733
1196
  }
734
- let raycastEvent = {
1197
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1198
+ const raycastEvent = {
735
1199
  ...hit,
736
1200
  ...extractEventProps,
737
1201
  pointer,
@@ -741,18 +1205,19 @@ function createEvents(store) {
741
1205
  unprojectedPoint,
742
1206
  ray: raycaster.ray,
743
1207
  camera,
1208
+ pointerId: eventPointerId,
744
1209
  // Hijack stopPropagation, which just sets a flag
745
1210
  stopPropagation() {
746
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1211
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
747
1212
  if (
748
1213
  // ...if this pointer hasn't been captured
749
- !capturesForPointer || // ... or if the hit object is capturing the pointer
750
- capturesForPointer.has(hit.eventObject)
1214
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1215
+ pointerState.captured.has(hit.eventObject)
751
1216
  ) {
752
1217
  raycastEvent.stopped = localState.stopped = true;
753
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1218
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
754
1219
  const higher = intersections.slice(0, intersections.indexOf(hit));
755
- cancelPointer([...higher, hit]);
1220
+ cancelPointer([...higher, hit], eventPointerId);
756
1221
  }
757
1222
  }
758
1223
  },
@@ -768,15 +1233,18 @@ function createEvents(store) {
768
1233
  }
769
1234
  return intersections;
770
1235
  }
771
- function cancelPointer(intersections) {
1236
+ function cancelPointer(intersections, pointerId) {
772
1237
  const { internal } = store.getState();
773
- for (const hoveredObj of internal.hovered.values()) {
1238
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1239
+ const pointerState = internal.pointerMap.get(pid);
1240
+ if (!pointerState) return;
1241
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
774
1242
  if (!intersections.length || !intersections.find(
775
1243
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
776
1244
  )) {
777
1245
  const eventObject = hoveredObj.eventObject;
778
1246
  const instance = eventObject.__r3f;
779
- internal.hovered.delete(makeId(hoveredObj));
1247
+ pointerState.hovered.delete(hoveredId);
780
1248
  if (instance?.eventCount) {
781
1249
  const handlers = instance.handlers;
782
1250
  const data = { ...hoveredObj, intersections };
@@ -805,41 +1273,118 @@ function createEvents(store) {
805
1273
  instance?.handlers.onDropMissed?.(event);
806
1274
  }
807
1275
  }
1276
+ function cleanupPointer(pointerId) {
1277
+ const { internal } = store.getState();
1278
+ const pointerState = internal.pointerMap.get(pointerId);
1279
+ if (pointerState) {
1280
+ for (const [, hoveredObj] of pointerState.hovered) {
1281
+ const eventObject = hoveredObj.eventObject;
1282
+ const instance = eventObject.__r3f;
1283
+ if (instance?.eventCount) {
1284
+ const handlers = instance.handlers;
1285
+ const data = { ...hoveredObj, intersections: [] };
1286
+ handlers.onPointerOut?.(data);
1287
+ handlers.onPointerLeave?.(data);
1288
+ }
1289
+ }
1290
+ internal.pointerMap.delete(pointerId);
1291
+ }
1292
+ internal.pointerDirty.delete(pointerId);
1293
+ }
1294
+ function processDeferredPointer(event, pointerId) {
1295
+ const state = store.getState();
1296
+ const { internal } = state;
1297
+ if (!state.events.enabled) return;
1298
+ const filter = filterPointerEvents;
1299
+ const hits = intersect(event, filter);
1300
+ cancelPointer(hits, pointerId);
1301
+ function onIntersect(data) {
1302
+ const eventObject = data.eventObject;
1303
+ const instance = eventObject.__r3f;
1304
+ if (!instance?.eventCount) return;
1305
+ const handlers = instance.handlers;
1306
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1307
+ const id = makeId(data);
1308
+ const pointerState = getPointerState(internal, pointerId);
1309
+ const hoveredItem = pointerState.hovered.get(id);
1310
+ if (!hoveredItem) {
1311
+ pointerState.hovered.set(id, data);
1312
+ handlers.onPointerOver?.(data);
1313
+ handlers.onPointerEnter?.(data);
1314
+ } else if (hoveredItem.stopped) {
1315
+ data.stopPropagation();
1316
+ }
1317
+ }
1318
+ handlers.onPointerMove?.(data);
1319
+ }
1320
+ handleIntersects(hits, event, 0, onIntersect);
1321
+ }
808
1322
  function handlePointer(name) {
809
1323
  switch (name) {
810
1324
  case "onPointerLeave":
811
- case "onPointerCancel":
812
1325
  case "onDragLeave":
813
1326
  return () => cancelPointer([]);
1327
+ // Global cancel of these events
1328
+ case "onPointerCancel":
1329
+ return (event) => {
1330
+ const pointerId = getPointerId(event);
1331
+ cleanupPointer(pointerId);
1332
+ };
814
1333
  case "onLostPointerCapture":
815
1334
  return (event) => {
816
1335
  const { internal } = store.getState();
817
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1336
+ const pointerId = getPointerId(event);
1337
+ const pointerState = internal.pointerMap.get(pointerId);
1338
+ if (pointerState?.captured.size) {
818
1339
  requestAnimationFrame(() => {
819
- if (internal.capturedMap.has(event.pointerId)) {
820
- internal.capturedMap.delete(event.pointerId);
821
- cancelPointer([]);
1340
+ const pointerState2 = internal.pointerMap.get(pointerId);
1341
+ if (pointerState2?.captured.size) {
1342
+ pointerState2.captured.clear();
822
1343
  }
1344
+ cancelPointer([], pointerId);
823
1345
  });
824
1346
  }
825
1347
  };
826
1348
  }
827
1349
  return function handleEvent(event) {
828
1350
  const state = store.getState();
829
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1351
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1352
+ const pointerId = getPointerId(event);
830
1353
  internal.lastEvent.current = event;
831
- if (!state.events.enabled) return;
1354
+ if (!events.enabled) return;
832
1355
  const isPointerMove = name === "onPointerMove";
833
1356
  const isDragOver = name === "onDragOver";
834
1357
  const isDrop = name === "onDrop";
835
1358
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1359
+ const isPointerDown = name === "onPointerDown";
1360
+ const isPointerUp = name === "onPointerUp";
1361
+ const isWheel = name === "onWheel";
1362
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1363
+ if (isPointerMove && canDeferRaycasts) {
1364
+ events.compute?.(event, state);
1365
+ internal.pointerDirty.set(pointerId, event);
1366
+ return;
1367
+ }
1368
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1369
+ events.compute?.(event, state);
1370
+ internal.pointerDirty.set(pointerId, event);
1371
+ return;
1372
+ }
1373
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1374
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1375
+ internal.pointerDirty.delete(pointerId);
1376
+ processDeferredPointer(deferredEvent, pointerId);
1377
+ }
836
1378
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
837
1379
  const hits = intersect(event, filter);
838
- const delta = isClickEvent ? calculateDistance(event) : 0;
839
- if (name === "onPointerDown") {
840
- internal.initialClick = [event.offsetX, event.offsetY];
841
- internal.initialHits = hits.map((hit) => hit.eventObject);
842
- }
1380
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1381
+ if (isPointerDown) {
1382
+ const pointerState2 = getPointerState(internal, pointerId);
1383
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1384
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1385
+ }
1386
+ const pointerState = internal.pointerMap.get(pointerId);
1387
+ const initialHits = pointerState?.initialHits ?? [];
843
1388
  if (isClickEvent && !hits.length) {
844
1389
  if (delta <= 2) {
845
1390
  pointerMissed(event, internal.interaction);
@@ -854,7 +1399,9 @@ function createEvents(store) {
854
1399
  dropMissed(event, internal.interaction);
855
1400
  if (onDropMissed) onDropMissed(event);
856
1401
  }
857
- if (isPointerMove || isDragOver) cancelPointer(hits);
1402
+ if (isPointerMove || isDragOver) {
1403
+ cancelPointer(hits, pointerId);
1404
+ }
858
1405
  function onIntersect(data) {
859
1406
  const eventObject = data.eventObject;
860
1407
  const instance = eventObject.__r3f;
@@ -863,9 +1410,10 @@ function createEvents(store) {
863
1410
  if (isPointerMove) {
864
1411
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
865
1412
  const id = makeId(data);
866
- const hoveredItem = internal.hovered.get(id);
1413
+ const pointerState2 = getPointerState(internal, pointerId);
1414
+ const hoveredItem = pointerState2.hovered.get(id);
867
1415
  if (!hoveredItem) {
868
- internal.hovered.set(id, data);
1416
+ pointerState2.hovered.set(id, data);
869
1417
  handlers.onPointerOver?.(data);
870
1418
  handlers.onPointerEnter?.(data);
871
1419
  } else if (hoveredItem.stopped) {
@@ -875,9 +1423,10 @@ function createEvents(store) {
875
1423
  handlers.onPointerMove?.(data);
876
1424
  } else if (isDragOver) {
877
1425
  const id = makeId(data);
878
- const hoveredItem = internal.hovered.get(id);
1426
+ const pointerState2 = getPointerState(internal, pointerId);
1427
+ const hoveredItem = pointerState2.hovered.get(id);
879
1428
  if (!hoveredItem) {
880
- internal.hovered.set(id, data);
1429
+ pointerState2.hovered.set(id, data);
881
1430
  handlers.onDragOverEnter?.(data);
882
1431
  } else if (hoveredItem.stopped) {
883
1432
  data.stopPropagation();
@@ -888,18 +1437,18 @@ function createEvents(store) {
888
1437
  } else {
889
1438
  const handler = handlers[name];
890
1439
  if (handler) {
891
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1440
+ if (!isClickEvent || initialHits.includes(eventObject)) {
892
1441
  pointerMissed(
893
1442
  event,
894
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1443
+ internal.interaction.filter((object) => !initialHits.includes(object))
895
1444
  );
896
1445
  handler(data);
897
1446
  }
898
1447
  } else {
899
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1448
+ if (isClickEvent && initialHits.includes(eventObject)) {
900
1449
  pointerMissed(
901
1450
  event,
902
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1451
+ internal.interaction.filter((object) => !initialHits.includes(object))
903
1452
  );
904
1453
  }
905
1454
  }
@@ -908,7 +1457,15 @@ function createEvents(store) {
908
1457
  handleIntersects(hits, event, delta, onIntersect);
909
1458
  };
910
1459
  }
911
- return { handlePointer };
1460
+ function flushDeferredPointers() {
1461
+ const { internal, events } = store.getState();
1462
+ if (!events.frameTimedRaycasts) return;
1463
+ for (const [pointerId, event] of internal.pointerDirty) {
1464
+ processDeferredPointer(event, pointerId);
1465
+ }
1466
+ internal.pointerDirty.clear();
1467
+ }
1468
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
912
1469
  }
913
1470
  const DOM_EVENTS = {
914
1471
  onClick: ["click", false],
@@ -927,11 +1484,16 @@ const DOM_EVENTS = {
927
1484
  onLostPointerCapture: ["lostpointercapture", true]
928
1485
  };
929
1486
  function createPointerEvents(store) {
930
- const { handlePointer } = createEvents(store);
1487
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1488
+ let nextXRPointerId = XR_POINTER_ID_START;
1489
+ const xrPointers = /* @__PURE__ */ new Map();
931
1490
  return {
932
1491
  priority: 1,
933
1492
  enabled: true,
934
- compute(event, state, previous) {
1493
+ frameTimedRaycasts: true,
1494
+ alwaysFireOnScroll: true,
1495
+ updateOnFrame: false,
1496
+ compute(event, state) {
935
1497
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
936
1498
  state.raycaster.setFromCamera(state.pointer, state.camera);
937
1499
  },
@@ -940,11 +1502,33 @@ function createPointerEvents(store) {
940
1502
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
941
1503
  {}
942
1504
  ),
943
- update: () => {
1505
+ update: (pointerId) => {
1506
+ const { events, internal } = store.getState();
1507
+ if (!events.handlers) return;
1508
+ if (pointerId !== void 0) {
1509
+ const event = internal.pointerDirty.get(pointerId);
1510
+ if (event) {
1511
+ internal.pointerDirty.delete(pointerId);
1512
+ processDeferredPointer(event, pointerId);
1513
+ } else if (internal.lastEvent?.current) {
1514
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1515
+ }
1516
+ } else {
1517
+ flushDeferredPointers();
1518
+ if (internal.lastEvent?.current) {
1519
+ events.handlers.onPointerMove(internal.lastEvent.current);
1520
+ }
1521
+ }
1522
+ },
1523
+ flush: () => {
944
1524
  const { events, internal } = store.getState();
945
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1525
+ flushDeferredPointers();
1526
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1527
+ events.handlers.onPointerMove(internal.lastEvent.current);
1528
+ }
946
1529
  },
947
1530
  connect: (target) => {
1531
+ if (!target) return;
948
1532
  const { set, events } = store.getState();
949
1533
  events.disconnect?.();
950
1534
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -968,6 +1552,32 @@ function createPointerEvents(store) {
968
1552
  }
969
1553
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
970
1554
  }
1555
+ },
1556
+ registerPointer: (config) => {
1557
+ const pointerId = nextXRPointerId++;
1558
+ xrPointers.set(pointerId, config);
1559
+ const { internal } = store.getState();
1560
+ getPointerState(internal, pointerId);
1561
+ return pointerId;
1562
+ },
1563
+ unregisterPointer: (pointerId) => {
1564
+ xrPointers.delete(pointerId);
1565
+ const { internal } = store.getState();
1566
+ const pointerState = internal.pointerMap.get(pointerId);
1567
+ if (pointerState) {
1568
+ for (const [, hoveredObj] of pointerState.hovered) {
1569
+ const eventObject = hoveredObj.eventObject;
1570
+ const instance = eventObject.__r3f;
1571
+ if (instance?.eventCount) {
1572
+ const handlers = instance.handlers;
1573
+ const data = { ...hoveredObj, intersections: [] };
1574
+ handlers.onPointerOut?.(data);
1575
+ handlers.onPointerLeave?.(data);
1576
+ }
1577
+ }
1578
+ internal.pointerMap.delete(pointerId);
1579
+ }
1580
+ internal.pointerDirty.delete(pointerId);
971
1581
  }
972
1582
  };
973
1583
  }
@@ -1058,23 +1668,29 @@ const createStore = (invalidate, advance) => {
1058
1668
  set,
1059
1669
  get,
1060
1670
  // Mock objects that have to be configured
1671
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
1672
+ primaryStore: null,
1061
1673
  gl: null,
1062
1674
  renderer: null,
1063
1675
  camera: null,
1064
1676
  frustum: new Frustum(),
1065
1677
  autoUpdateFrustum: true,
1066
1678
  raycaster: null,
1067
- events: { priority: 1, enabled: true, connected: false },
1679
+ events: {
1680
+ priority: 1,
1681
+ enabled: true,
1682
+ connected: false,
1683
+ frameTimedRaycasts: true,
1684
+ alwaysFireOnScroll: true,
1685
+ updateOnFrame: false
1686
+ },
1068
1687
  scene: null,
1069
1688
  rootScene: null,
1070
1689
  xr: null,
1071
1690
  inspector: null,
1072
1691
  invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1073
1692
  advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1074
- legacy: false,
1075
- linear: false,
1076
- flat: false,
1077
- textureColorSpace: "srgb",
1693
+ textureColorSpace: SRGBColorSpace,
1078
1694
  isLegacy: false,
1079
1695
  webGPUSupported: false,
1080
1696
  isNative: false,
@@ -1114,10 +1730,40 @@ const createStore = (invalidate, advance) => {
1114
1730
  getCurrentViewport
1115
1731
  },
1116
1732
  setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1117
- setSize: (width, height, top = 0, left = 0) => {
1118
- const camera = get().camera;
1119
- const size = { width, height, top, left };
1120
- set((state2) => ({ size, viewport: { ...state2.viewport, ...getCurrentViewport(camera, defaultTarget, size) } }));
1733
+ setSize: (width, height, top, left) => {
1734
+ const state2 = get();
1735
+ if (width === void 0) {
1736
+ set({ _sizeImperative: false });
1737
+ if (state2._sizeProps) {
1738
+ const { width: propW, height: propH } = state2._sizeProps;
1739
+ if (propW !== void 0 || propH !== void 0) {
1740
+ const currentSize = state2.size;
1741
+ const newSize = {
1742
+ width: propW ?? currentSize.width,
1743
+ height: propH ?? currentSize.height,
1744
+ top: currentSize.top,
1745
+ left: currentSize.left
1746
+ };
1747
+ set((s) => ({
1748
+ size: newSize,
1749
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1750
+ }));
1751
+ getScheduler().invalidate();
1752
+ }
1753
+ }
1754
+ return;
1755
+ }
1756
+ const w = width;
1757
+ const h = height ?? width;
1758
+ const t = top ?? state2.size.top;
1759
+ const l = left ?? state2.size.left;
1760
+ const size = { width: w, height: h, top: t, left: l };
1761
+ set((s) => ({
1762
+ size,
1763
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1764
+ _sizeImperative: true
1765
+ }));
1766
+ getScheduler().invalidate();
1121
1767
  },
1122
1768
  setDpr: (dpr) => set((state2) => {
1123
1769
  const resolved = calculateDpr(dpr);
@@ -1128,22 +1774,32 @@ const createStore = (invalidate, advance) => {
1128
1774
  },
1129
1775
  setError: (error) => set(() => ({ error })),
1130
1776
  error: null,
1131
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1777
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
1132
1778
  uniforms: {},
1133
1779
  nodes: {},
1780
+ buffers: {},
1781
+ gpuStorage: {},
1134
1782
  textures: /* @__PURE__ */ new Map(),
1135
- postProcessing: null,
1783
+ _textureRefs: /* @__PURE__ */ new Map(),
1784
+ renderPipeline: null,
1136
1785
  passes: {},
1786
+ _hmrVersion: 0,
1787
+ _sizeImperative: false,
1788
+ _sizeProps: null,
1137
1789
  previousRoot: void 0,
1138
1790
  internal: {
1139
1791
  // Events
1140
1792
  interaction: [],
1141
- hovered: /* @__PURE__ */ new Map(),
1142
1793
  subscribers: [],
1794
+ // Per-pointer state (new unified structure)
1795
+ pointerMap: /* @__PURE__ */ new Map(),
1796
+ pointerDirty: /* @__PURE__ */ new Map(),
1797
+ lastEvent: React.createRef(),
1798
+ // Deprecated but kept for backwards compatibility
1799
+ hovered: /* @__PURE__ */ new Map(),
1143
1800
  initialClick: [0, 0],
1144
1801
  initialHits: [],
1145
1802
  capturedMap: /* @__PURE__ */ new Map(),
1146
- lastEvent: React.createRef(),
1147
1803
  // Visibility tracking (onFramed, onOccluded, onVisible)
1148
1804
  visibilityRegistry: /* @__PURE__ */ new Map(),
1149
1805
  // Occlusion system (WebGPU only)
@@ -1226,13 +1882,22 @@ const createStore = (invalidate, advance) => {
1226
1882
  rootStore.subscribe(() => {
1227
1883
  const { camera, size, viewport, set, internal } = rootStore.getState();
1228
1884
  const actualRenderer = internal.actualRenderer;
1885
+ const canvasTarget = internal.canvasTarget;
1229
1886
  if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1230
1887
  oldSize = size;
1231
1888
  oldDpr = viewport.dpr;
1232
1889
  updateCamera(camera, size);
1233
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1234
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1235
- actualRenderer.setSize(size.width, size.height, updateStyle);
1890
+ if (internal.isSecondary && canvasTarget) {
1891
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1892
+ canvasTarget.setSize(size.width, size.height, false);
1893
+ } else {
1894
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1895
+ actualRenderer.setSize(size.width, size.height, false);
1896
+ if (canvasTarget) {
1897
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1898
+ canvasTarget.setSize(size.width, size.height, false);
1899
+ }
1900
+ }
1236
1901
  }
1237
1902
  if (camera !== oldCamera) {
1238
1903
  oldCamera = camera;
@@ -1303,1076 +1968,45 @@ useLoader.clear = function(loader, input) {
1303
1968
  };
1304
1969
  useLoader.loader = getLoader;
1305
1970
 
1306
- var __defProp$1 = Object.defineProperty;
1307
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1308
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1309
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1310
- class PhaseGraph {
1311
- constructor() {
1312
- /** Ordered list of phase nodes */
1313
- __publicField$1(this, "phases", []);
1314
- /** Quick lookup by name */
1315
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1316
- /** Cached ordered names (invalidated on changes) */
1317
- __publicField$1(this, "orderedNamesCache", null);
1318
- this.initializeDefaultPhases();
1319
- }
1320
- //* Initialization --------------------------------
1321
- initializeDefaultPhases() {
1322
- for (const name of DEFAULT_PHASES) {
1323
- const node = { name, isAutoGenerated: false };
1324
- this.phases.push(node);
1325
- this.phaseMap.set(name, node);
1326
- }
1327
- this.invalidateCache();
1328
- }
1329
- //* Public API --------------------------------
1330
- /**
1331
- * Add a named phase to the graph
1332
- * @param name - Phase name (must be unique)
1333
- * @param options - Position options (before or after another phase)
1334
- */
1335
- addPhase(name, options = {}) {
1336
- if (this.phaseMap.has(name)) {
1337
- console.warn(`[useFrame] Phase "${name}" already exists`);
1338
- return;
1339
- }
1340
- const { before, after } = options;
1341
- const node = { name, isAutoGenerated: false };
1342
- let insertIndex = this.phases.length;
1343
- const targetIndex = this.getPhaseIndex(before ?? after);
1344
- if (targetIndex !== -1) insertIndex = before ? targetIndex : targetIndex + 1;
1345
- else {
1346
- const constraintType = before ? "before" : "after";
1347
- console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
1348
- }
1349
- this.phases.splice(insertIndex, 0, node);
1350
- this.phaseMap.set(name, node);
1351
- this.invalidateCache();
1352
- }
1353
- /**
1354
- * Get ordered list of phase names
1355
- */
1356
- getOrderedPhases() {
1357
- if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
1358
- return this.orderedNamesCache;
1359
- }
1360
- /**
1361
- * Check if a phase exists
1362
- */
1363
- hasPhase(name) {
1364
- return this.phaseMap.has(name);
1365
- }
1366
- /**
1367
- * Get the index of a phase (-1 if not found)
1368
- */
1369
- getPhaseIndex(name) {
1370
- if (!name) return -1;
1371
- return this.phases.findIndex((p) => p.name === name);
1372
- }
1373
- /**
1374
- * Ensure a phase exists, creating an auto-generated one if needed.
1375
- * Used for resolving before/after constraints.
1376
- *
1377
- * @param name - The phase name to ensure exists
1378
- * @returns The phase name (may be auto-generated like 'before:render')
1379
- */
1380
- ensurePhase(name) {
1381
- if (this.phaseMap.has(name)) return name;
1382
- const node = { name, isAutoGenerated: true };
1383
- this.phases.push(node);
1384
- this.phaseMap.set(name, node);
1385
- this.invalidateCache();
1386
- return name;
1387
- }
1388
- /**
1389
- * Resolve where a job with before/after constraints should go.
1390
- * Creates auto-generated phases if needed.
1391
- *
1392
- * @param before - Phase(s) to run before
1393
- * @param after - Phase(s) to run after
1394
- * @returns The resolved phase name
1395
- */
1396
- resolveConstraintPhase(before, after) {
1397
- const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
1398
- const afterArr = after ? Array.isArray(after) ? after : [after] : [];
1399
- if (beforeArr.length > 0) {
1400
- return this.ensureAutoPhase(beforeArr[0], "before", 0);
1401
- }
1402
- if (afterArr.length > 0) {
1403
- return this.ensureAutoPhase(afterArr[0], "after", 1);
1404
- }
1405
- return "update";
1406
- }
1407
- /**
1408
- * Ensure an auto-generated phase exists relative to a target phase.
1409
- * Creates the phase if it doesn't exist, inserting it at the correct position.
1410
- *
1411
- * @param target - The target phase name to position relative to
1412
- * @param prefix - Prefix for auto-generated phase name ('before' or 'after')
1413
- * @param offset - Insertion offset (0 for before, 1 for after)
1414
- * @returns The auto-generated phase name
1415
- */
1416
- ensureAutoPhase(target, prefix, offset) {
1417
- const autoName = `${prefix}:${target}`;
1418
- if (this.phaseMap.has(autoName)) return autoName;
1419
- const node = { name: autoName, isAutoGenerated: true };
1420
- const targetIndex = this.getPhaseIndex(target);
1421
- if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
1422
- else this.phases.push(node);
1423
- this.phaseMap.set(autoName, node);
1424
- this.invalidateCache();
1425
- return autoName;
1426
- }
1427
- // Internal --------------------------------
1428
- invalidateCache() {
1429
- this.orderedNamesCache = null;
1430
- }
1431
- }
1432
-
1433
- function rebuildSortedJobs(jobs, phaseGraph) {
1434
- const orderedPhases = phaseGraph.getOrderedPhases();
1435
- const buckets = /* @__PURE__ */ new Map();
1436
- for (const phase of orderedPhases) {
1437
- buckets.set(phase, []);
1438
- }
1439
- for (const job of jobs.values()) {
1440
- if (!job.enabled) continue;
1441
- let bucket = buckets.get(job.phase);
1442
- if (!bucket) {
1443
- bucket = [];
1444
- buckets.set(job.phase, bucket);
1445
- }
1446
- bucket.push(job);
1447
- }
1448
- const sortedBuckets = [];
1449
- for (const phase of orderedPhases) {
1450
- const bucket = buckets.get(phase);
1451
- if (!bucket || bucket.length === 0) continue;
1452
- bucket.sort((a, b) => {
1453
- if (a.priority !== b.priority) return b.priority - a.priority;
1454
- return a.index - b.index;
1455
- });
1456
- sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
1457
- }
1458
- for (const [phase, bucket] of buckets) {
1459
- if (!orderedPhases.includes(phase) && bucket.length > 0) {
1460
- bucket.sort((a, b) => {
1461
- if (a.priority !== b.priority) return b.priority - a.priority;
1462
- return a.index - b.index;
1463
- });
1464
- sortedBuckets.push(bucket);
1465
- }
1466
- }
1467
- return sortedBuckets.flat();
1468
- }
1469
- function hasCrossJobConstraints(bucket) {
1470
- const jobIds = new Set(bucket.map((j) => j.id));
1471
- for (const job of bucket) {
1472
- for (const ref of job.before) {
1473
- if (jobIds.has(ref)) return true;
1474
- }
1475
- for (const ref of job.after) {
1476
- if (jobIds.has(ref)) return true;
1477
- }
1478
- }
1479
- return false;
1480
- }
1481
- function topologicalSort(jobs) {
1482
- const n = jobs.length;
1483
- if (n <= 1) return jobs;
1484
- const jobMap = /* @__PURE__ */ new Map();
1485
- const inDegree = /* @__PURE__ */ new Map();
1486
- const adjacency = /* @__PURE__ */ new Map();
1487
- for (const job of jobs) {
1488
- jobMap.set(job.id, job);
1489
- inDegree.set(job.id, 0);
1490
- adjacency.set(job.id, []);
1491
- }
1492
- for (const job of jobs) {
1493
- for (const ref of job.before) {
1494
- if (jobMap.has(ref)) {
1495
- adjacency.get(job.id).push(ref);
1496
- inDegree.set(ref, inDegree.get(ref) + 1);
1497
- }
1498
- }
1499
- for (const ref of job.after) {
1500
- if (jobMap.has(ref)) {
1501
- adjacency.get(ref).push(job.id);
1502
- inDegree.set(job.id, inDegree.get(job.id) + 1);
1503
- }
1504
- }
1505
- }
1506
- const queue = [];
1507
- for (const job of jobs) {
1508
- if (inDegree.get(job.id) === 0) {
1509
- queue.push(job);
1510
- }
1511
- }
1512
- queue.sort((a, b) => {
1513
- if (a.priority !== b.priority) return b.priority - a.priority;
1514
- return a.index - b.index;
1515
- });
1516
- const result = [];
1517
- while (queue.length > 0) {
1518
- const job = queue.shift();
1519
- result.push(job);
1520
- const neighbors = adjacency.get(job.id) || [];
1521
- for (const neighborId of neighbors) {
1522
- const newDegree = inDegree.get(neighborId) - 1;
1523
- inDegree.set(neighborId, newDegree);
1524
- if (newDegree === 0) {
1525
- const neighbor = jobMap.get(neighborId);
1526
- insertSorted(queue, neighbor);
1527
- }
1528
- }
1529
- }
1530
- if (result.length !== n) {
1531
- console.warn("[useFrame] Circular dependency detected in job constraints");
1532
- const resultIds = new Set(result.map((j) => j.id));
1533
- for (const job of jobs) {
1534
- if (!resultIds.has(job.id)) result.push(job);
1535
- }
1536
- }
1537
- return result;
1538
- }
1539
- function insertSorted(arr, job) {
1540
- let i = 0;
1541
- while (i < arr.length) {
1542
- const cmp = arr[i];
1543
- if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
1544
- break;
1545
- }
1546
- i++;
1547
- }
1548
- arr.splice(i, 0, job);
1549
- }
1550
-
1551
- function shouldRun(job, now) {
1552
- if (!job.enabled) return false;
1553
- if (!job.fps) return true;
1554
- const minInterval = 1e3 / job.fps;
1555
- const lastRun = job.lastRun ?? 0;
1556
- const elapsed = now - lastRun;
1557
- if (elapsed < minInterval) return false;
1558
- if (job.drop) {
1559
- job.lastRun = now;
1560
- } else {
1561
- const steps = Math.floor(elapsed / minInterval);
1562
- job.lastRun = lastRun + steps * minInterval;
1563
- if (job.lastRun < now - minInterval) {
1564
- job.lastRun = now;
1565
- }
1566
- }
1567
- return true;
1568
- }
1569
- function resetJobTiming(job) {
1570
- job.lastRun = void 0;
1571
- }
1572
-
1573
- var __defProp = Object.defineProperty;
1574
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1575
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1576
- const hmrData = (() => {
1577
- if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
1578
- if (typeof import_meta_hot !== "undefined") return import_meta_hot;
1579
- try {
1580
- return (0, eval)("import.meta.hot");
1581
- } catch {
1582
- return void 0;
1583
- }
1584
- })();
1585
- const _Scheduler = class _Scheduler {
1586
- //* Constructor ================================
1587
- constructor() {
1588
- //* Critical State ================================
1589
- __publicField(this, "roots", /* @__PURE__ */ new Map());
1590
- __publicField(this, "phaseGraph");
1591
- __publicField(this, "loopState", {
1592
- running: false,
1593
- rafHandle: null,
1594
- lastTime: null,
1595
- // null = uninitialized, 0+ = valid timestamp
1596
- frameCount: 0,
1597
- elapsedTime: 0,
1598
- createdAt: performance.now()
1599
- });
1600
- __publicField(this, "stoppedTime", 0);
1601
- //* Private State ================================
1602
- __publicField(this, "nextRootIndex", 0);
1603
- __publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
1604
- __publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
1605
- __publicField(this, "nextGlobalIndex", 0);
1606
- __publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
1607
- __publicField(this, "nextJobIndex", 0);
1608
- __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1609
- __publicField(this, "pendingFrames", 0);
1610
- __publicField(this, "_frameloop", "always");
1611
- //* Independent Mode & Error Handling State ================================
1612
- __publicField(this, "_independent", false);
1613
- __publicField(this, "errorHandler", null);
1614
- __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1615
- //* Core Loop Execution Methods ================================
1616
- /**
1617
- * Main RAF loop callback.
1618
- * Executes frame, handles demand mode, and schedules next frame.
1619
- * @param {number} timestamp - RAF timestamp in milliseconds
1620
- * @returns {void}
1621
- * @private
1622
- */
1623
- __publicField(this, "loop", (timestamp) => {
1624
- if (!this.loopState.running) return;
1625
- this.executeFrame(timestamp);
1626
- if (this._frameloop === "demand") {
1627
- this.pendingFrames = Math.max(0, this.pendingFrames - 1);
1628
- if (this.pendingFrames === 0) {
1629
- this.notifyIdle(timestamp);
1630
- return this.stop();
1631
- }
1632
- }
1633
- this.loopState.rafHandle = requestAnimationFrame(this.loop);
1634
- });
1635
- this.phaseGraph = new PhaseGraph();
1636
- }
1637
- static get instance() {
1638
- return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1639
- }
1640
- static set instance(value) {
1641
- globalThis[_Scheduler.INSTANCE_KEY] = value;
1642
- }
1643
- /**
1644
- * Get the global scheduler instance (creates if doesn't exist).
1645
- * Uses HMR data to preserve instance across hot reloads.
1646
- * @returns {Scheduler} The singleton scheduler instance
1647
- */
1648
- static get() {
1649
- if (!_Scheduler.instance && hmrData?.data?.scheduler) {
1650
- _Scheduler.instance = hmrData.data.scheduler;
1651
- }
1652
- if (!_Scheduler.instance) {
1653
- _Scheduler.instance = new _Scheduler();
1654
- if (hmrData?.data) {
1655
- hmrData.data.scheduler = _Scheduler.instance;
1656
- }
1657
- }
1658
- return _Scheduler.instance;
1659
- }
1660
- /**
1661
- * Reset the singleton instance. Stops the loop and clears all state.
1662
- * Primarily used for testing to ensure clean state between tests.
1663
- * @returns {void}
1664
- */
1665
- static reset() {
1666
- if (_Scheduler.instance) {
1667
- _Scheduler.instance.stop();
1668
- _Scheduler.instance = null;
1669
- }
1670
- if (hmrData?.data) {
1671
- hmrData.data.scheduler = null;
1672
- }
1673
- }
1674
- //* Getters & Setters ================================
1675
- get phases() {
1676
- return this.phaseGraph.getOrderedPhases();
1677
- }
1678
- get frameloop() {
1679
- return this._frameloop;
1680
- }
1681
- set frameloop(mode) {
1682
- if (this._frameloop === mode) return;
1683
- const wasAlways = this._frameloop === "always";
1684
- this._frameloop = mode;
1685
- if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
1686
- else if (mode !== "always" && wasAlways) this.stop();
1687
- }
1688
- get isRunning() {
1689
- return this.loopState.running;
1690
- }
1691
- get isReady() {
1692
- return this.roots.size > 0;
1693
- }
1694
- get independent() {
1695
- return this._independent;
1696
- }
1697
- set independent(value) {
1698
- this._independent = value;
1699
- if (value) this.ensureDefaultRoot();
1700
- }
1701
- //* Root Management Methods ================================
1702
- /**
1703
- * Register a root (Canvas) with the scheduler.
1704
- * The first root to register starts the RAF loop (if frameloop='always').
1705
- * @param {string} id - Unique identifier for this root
1706
- * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1707
- * @returns {() => void} Unsubscribe function to remove this root
1708
- */
1709
- registerRoot(id, options = {}) {
1710
- if (this.roots.has(id)) {
1711
- console.warn(`[Scheduler] Root "${id}" already registered`);
1712
- return () => this.unregisterRoot(id);
1713
- }
1714
- const entry = {
1715
- id,
1716
- getState: options.getState ?? (() => ({})),
1717
- jobs: /* @__PURE__ */ new Map(),
1718
- sortedJobs: [],
1719
- needsRebuild: false
1720
- };
1721
- if (options.onError) {
1722
- this.errorHandler = options.onError;
1723
- }
1724
- this.roots.set(id, entry);
1725
- if (this.roots.size === 1) {
1726
- this.notifyRootReady();
1727
- if (this._frameloop === "always") this.start();
1728
- }
1729
- return () => this.unregisterRoot(id);
1730
- }
1731
- /**
1732
- * Unregister a root from the scheduler.
1733
- * Cleans up all job state listeners for this root's jobs.
1734
- * The last root to unregister stops the RAF loop.
1735
- * @param {string} id - The root ID to unregister
1736
- * @returns {void}
1737
- */
1738
- unregisterRoot(id) {
1739
- const root = this.roots.get(id);
1740
- if (!root) return;
1741
- for (const jobId of root.jobs.keys()) {
1742
- this.jobStateListeners.delete(jobId);
1743
- }
1744
- this.roots.delete(id);
1745
- if (this.roots.size === 0) {
1746
- this.stop();
1747
- this.errorHandler = null;
1748
- }
1749
- }
1750
- /**
1751
- * Subscribe to be notified when a root becomes available.
1752
- * Fires immediately if a root already exists.
1753
- * @param {() => void} callback - Function called when first root registers
1754
- * @returns {() => void} Unsubscribe function
1755
- */
1756
- onRootReady(callback) {
1757
- if (this.roots.size > 0) {
1758
- callback();
1759
- return () => {
1760
- };
1761
- }
1762
- this.rootReadyCallbacks.add(callback);
1763
- return () => this.rootReadyCallbacks.delete(callback);
1764
- }
1765
- /**
1766
- * Notify all registered root-ready callbacks.
1767
- * Called when the first root registers.
1768
- * @returns {void}
1769
- * @private
1770
- */
1771
- notifyRootReady() {
1772
- for (const cb of this.rootReadyCallbacks) {
1773
- try {
1774
- cb();
1775
- } catch (error) {
1776
- console.error("[Scheduler] Error in root-ready callback:", error);
1777
- }
1778
- }
1779
- this.rootReadyCallbacks.clear();
1780
- }
1781
- /**
1782
- * Ensure a default root exists for independent mode.
1783
- * Creates a minimal root with no state provider.
1784
- * @returns {void}
1785
- * @private
1786
- */
1787
- ensureDefaultRoot() {
1788
- if (!this.roots.has("__default__")) {
1789
- this.registerRoot("__default__");
1790
- }
1791
- }
1792
- /**
1793
- * Trigger error handling for job errors.
1794
- * Uses the bound error handler if available, otherwise logs to console.
1795
- * @param {Error} error - The error to handle
1796
- * @returns {void}
1797
- */
1798
- triggerError(error) {
1799
- if (this.errorHandler) this.errorHandler(error);
1800
- else console.error("[Scheduler]", error);
1801
- }
1802
- //* Phase Management Methods ================================
1803
- /**
1804
- * Add a named phase to the scheduler's execution order.
1805
- * Marks all roots for rebuild to incorporate the new phase.
1806
- * @param {string} name - The phase name (e.g., 'physics', 'postprocess')
1807
- * @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
1808
- * @returns {void}
1809
- * @example
1810
- * scheduler.addPhase('physics', { before: 'update' });
1811
- * scheduler.addPhase('postprocess', { after: 'render' });
1812
- */
1813
- addPhase(name, options) {
1814
- this.phaseGraph.addPhase(name, options);
1815
- for (const root of this.roots.values()) {
1816
- root.needsRebuild = true;
1817
- }
1818
- }
1819
- /**
1820
- * Check if a phase exists in the scheduler.
1821
- * @param {string} name - The phase name to check
1822
- * @returns {boolean} True if the phase exists
1823
- */
1824
- hasPhase(name) {
1825
- return this.phaseGraph.hasPhase(name);
1826
- }
1827
- //* Global Job Registration Methods (Deprecated APIs) ================================
1828
- /**
1829
- * Register a global job that runs once per frame (not per-root).
1830
- * Used internally by deprecated addEffect/addAfterEffect APIs.
1831
- * @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
1832
- * @param {string} id - Unique identifier for this global job
1833
- * @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
1834
- * @returns {() => void} Unsubscribe function to remove this global job
1835
- * @deprecated Use useFrame with phases instead
1836
- */
1837
- registerGlobal(phase, id, callback) {
1838
- const job = { id, callback };
1839
- if (phase === "before") {
1840
- this.globalBeforeJobs.set(id, job);
1841
- } else {
1842
- this.globalAfterJobs.set(id, job);
1843
- }
1844
- return () => {
1845
- if (phase === "before") this.globalBeforeJobs.delete(id);
1846
- else this.globalAfterJobs.delete(id);
1847
- };
1848
- }
1849
- //* Idle Callback Methods (Deprecated API) ================================
1850
- /**
1851
- * Register an idle callback that fires when the loop stops.
1852
- * Used internally by deprecated addTail API.
1853
- * @param {(timestamp: number) => void} callback - Function called when loop becomes idle
1854
- * @returns {() => void} Unsubscribe function to remove this idle callback
1855
- * @deprecated Use demand mode with invalidate() instead
1856
- */
1857
- onIdle(callback) {
1858
- this.idleCallbacks.add(callback);
1859
- return () => this.idleCallbacks.delete(callback);
1860
- }
1861
- /**
1862
- * Notify all registered idle callbacks.
1863
- * Called when the loop stops in demand mode.
1864
- * @param {number} timestamp - The RAF timestamp when idle occurred
1865
- * @returns {void}
1866
- * @private
1867
- */
1868
- notifyIdle(timestamp) {
1869
- for (const cb of this.idleCallbacks) {
1870
- try {
1871
- cb(timestamp);
1872
- } catch (error) {
1873
- console.error("[Scheduler] Error in idle callback:", error);
1874
- }
1875
- }
1876
- }
1877
- //* Job Registration & Management Methods ================================
1878
- /**
1879
- * Register a job (frame callback) with a specific root.
1880
- * This is the core registration method used by useFrame internally.
1881
- * @param {FrameNextCallback} callback - The function to call each frame
1882
- * @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
1883
- * @param {string} [options.rootId] - Target root ID (defaults to first registered root)
1884
- * @param {string} [options.id] - Unique job ID (auto-generated if not provided)
1885
- * @param {string} [options.phase] - Execution phase (defaults to 'update')
1886
- * @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
1887
- * @param {number} [options.fps] - FPS throttle limit
1888
- * @param {boolean} [options.drop] - Drop frames when behind (default true)
1889
- * @param {boolean} [options.enabled] - Whether job is active (default true)
1890
- * @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
1891
- * @returns {() => void} Unsubscribe function to remove this job
1892
- */
1893
- register(callback, options = {}) {
1894
- const rootId = options.rootId;
1895
- const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
1896
- if (!root) {
1897
- console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
1898
- return () => {
1899
- };
1900
- }
1901
- const id = options.id ?? this.generateJobId();
1902
- let phase = options.phase ?? "update";
1903
- if (!options.phase && (options.before || options.after)) {
1904
- phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
1905
- }
1906
- const before = this.normalizeConstraints(options.before);
1907
- const after = this.normalizeConstraints(options.after);
1908
- const job = {
1909
- id,
1910
- callback,
1911
- phase,
1912
- before,
1913
- after,
1914
- priority: options.priority ?? 0,
1915
- index: this.nextJobIndex++,
1916
- fps: options.fps,
1917
- drop: options.drop ?? true,
1918
- enabled: options.enabled ?? true,
1919
- system: options.system ?? false
1920
- };
1921
- if (root.jobs.has(id)) {
1922
- console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
1923
- }
1924
- root.jobs.set(id, job);
1925
- root.needsRebuild = true;
1926
- return () => this.unregister(id, root.id);
1927
- }
1928
- /**
1929
- * Unregister a job by its ID.
1930
- * Searches all roots if rootId is not provided.
1931
- * @param {string} id - The job ID to unregister
1932
- * @param {string} [rootId] - Optional root ID to search (searches all if not provided)
1933
- * @returns {void}
1934
- */
1935
- unregister(id, rootId) {
1936
- const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
1937
- if (root?.jobs.delete(id)) {
1938
- root.needsRebuild = true;
1939
- this.jobStateListeners.delete(id);
1940
- }
1941
- }
1942
- /**
1943
- * Update a job's options dynamically.
1944
- * Searches all roots to find the job by ID.
1945
- * Phase/constraint changes trigger a rebuild of the sorted job list.
1946
- * @param {string} id - The job ID to update
1947
- * @param {Partial<JobOptions>} options - The options to update
1948
- * @returns {void}
1949
- */
1950
- updateJob(id, options) {
1951
- let job;
1952
- let root;
1953
- for (const r of this.roots.values()) {
1954
- job = r.jobs.get(id);
1955
- if (job) {
1956
- root = r;
1957
- break;
1958
- }
1959
- }
1960
- if (!job || !root) return;
1961
- if (options.priority !== void 0) job.priority = options.priority;
1962
- if (options.fps !== void 0) job.fps = options.fps;
1963
- if (options.drop !== void 0) job.drop = options.drop;
1964
- if (options.enabled !== void 0) {
1965
- const wasEnabled = job.enabled;
1966
- job.enabled = options.enabled;
1967
- if (!wasEnabled && job.enabled) resetJobTiming(job);
1968
- if (wasEnabled !== job.enabled) root.needsRebuild = true;
1969
- }
1970
- if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
1971
- if (options.phase) job.phase = options.phase;
1972
- if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
1973
- if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
1974
- root.needsRebuild = true;
1975
- }
1976
- }
1977
- //* Job State Management Methods ================================
1978
- /**
1979
- * Check if a job is currently paused (disabled).
1980
- * @param {string} id - The job ID to check
1981
- * @returns {boolean} True if the job exists and is paused
1982
- */
1983
- isJobPaused(id) {
1984
- for (const root of this.roots.values()) {
1985
- const job = root.jobs.get(id);
1986
- if (job) return !job.enabled;
1987
- }
1988
- return false;
1989
- }
1990
- /**
1991
- * Subscribe to state changes for a specific job.
1992
- * Listener is called when job is paused or resumed.
1993
- * @param {string} id - The job ID to subscribe to
1994
- * @param {() => void} listener - Callback invoked on state changes
1995
- * @returns {() => void} Unsubscribe function
1996
- */
1997
- subscribeJobState(id, listener) {
1998
- if (!this.jobStateListeners.has(id)) {
1999
- this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
2000
- }
2001
- this.jobStateListeners.get(id).add(listener);
2002
- return () => {
2003
- this.jobStateListeners.get(id)?.delete(listener);
2004
- if (this.jobStateListeners.get(id)?.size === 0) {
2005
- this.jobStateListeners.delete(id);
2006
- }
2007
- };
2008
- }
2009
- /**
2010
- * Notify all listeners that a job's state has changed.
2011
- * @param {string} id - The job ID that changed
2012
- * @returns {void}
2013
- * @private
2014
- */
2015
- notifyJobStateChange(id) {
2016
- this.jobStateListeners.get(id)?.forEach((listener) => listener());
2017
- }
2018
- /**
2019
- * Pause a job by ID (sets enabled=false).
2020
- * Notifies any subscribed state listeners.
2021
- * @param {string} id - The job ID to pause
2022
- * @returns {void}
2023
- */
2024
- pauseJob(id) {
2025
- this.updateJob(id, { enabled: false });
2026
- this.notifyJobStateChange(id);
2027
- }
2028
- /**
2029
- * Resume a paused job by ID (sets enabled=true).
2030
- * Resets job timing to prevent frame accumulation.
2031
- * Notifies any subscribed state listeners.
2032
- * @param {string} id - The job ID to resume
2033
- * @returns {void}
2034
- */
2035
- resumeJob(id) {
2036
- this.updateJob(id, { enabled: true });
2037
- this.notifyJobStateChange(id);
2038
- }
2039
- //* Frame Loop Control Methods ================================
2040
- /**
2041
- * Start the requestAnimationFrame loop.
2042
- * Resets timing state (elapsedTime, frameCount) on start.
2043
- * No-op if already running.
2044
- * @returns {void}
2045
- */
2046
- start() {
2047
- if (this.loopState.running) return;
2048
- const { elapsedTime, createdAt } = this.loopState;
2049
- let adjustedCreated = 0;
2050
- if (this.stoppedTime > 0) {
2051
- adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
2052
- this.stoppedTime = 0;
2053
- }
2054
- Object.assign(this.loopState, {
2055
- running: true,
2056
- elapsedTime: elapsedTime ?? 0,
2057
- lastTime: performance.now(),
2058
- createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
2059
- frameCount: 0,
2060
- rafHandle: requestAnimationFrame(this.loop)
2061
- });
2062
- }
2063
- /**
2064
- * Stop the requestAnimationFrame loop.
2065
- * Cancels any pending RAF callback.
2066
- * No-op if not running.
2067
- * @returns {void}
2068
- */
2069
- stop() {
2070
- if (!this.loopState.running) return;
2071
- this.loopState.running = false;
2072
- if (this.loopState.rafHandle !== null) {
2073
- cancelAnimationFrame(this.loopState.rafHandle);
2074
- this.loopState.rafHandle = null;
2075
- }
2076
- this.stoppedTime = performance.now();
2077
- }
2078
- /**
2079
- * Request frames to be rendered in demand mode.
2080
- * Accumulates pending frames (capped at 60) and starts the loop if not running.
2081
- * No-op if frameloop is not 'demand'.
2082
- * @param {number} [frames=1] - Number of frames to request
2083
- * @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
2084
- * - `false` (default): Sets pending frames to the specified value (replaces existing count)
2085
- * - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
2086
- * @returns {void}
2087
- * @example
2088
- * // Request a single frame render
2089
- * scheduler.invalidate();
2090
- *
2091
- * @example
2092
- * // Request 5 frames (e.g., for animations)
2093
- * scheduler.invalidate(5);
2094
- *
2095
- * @example
2096
- * // Set pending frames to exactly 3 (don't stack with existing)
2097
- * scheduler.invalidate(3, false);
2098
- *
2099
- * @example
2100
- * // Add 2 more frames to existing pending count
2101
- * scheduler.invalidate(2, true);
2102
- */
2103
- invalidate(frames = 1, stackFrames = false) {
2104
- if (this._frameloop !== "demand") return;
2105
- const baseFrames = stackFrames ? this.pendingFrames : 0;
2106
- this.pendingFrames = Math.min(60, baseFrames + frames);
2107
- if (!this.loopState.running && this.pendingFrames > 0) this.start();
2108
- }
2109
- /**
2110
- * Reset timing state for deterministic testing.
2111
- * Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
2112
- * @returns {void}
2113
- */
2114
- resetTiming() {
2115
- this.loopState.lastTime = null;
2116
- this.loopState.frameCount = 0;
2117
- this.loopState.elapsedTime = 0;
2118
- this.loopState.createdAt = performance.now();
2119
- }
2120
- //* Manual Stepping Methods ================================
2121
- /**
2122
- * Manually execute a single frame for all roots.
2123
- * Useful for frameloop='never' mode or testing scenarios.
2124
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2125
- * @returns {void}
2126
- * @example
2127
- * // Manual control mode
2128
- * scheduler.frameloop = 'never';
2129
- * scheduler.step(); // Execute one frame
2130
- */
2131
- step(timestamp) {
2132
- const now = timestamp ?? performance.now();
2133
- this.executeFrame(now);
2134
- }
2135
- /**
2136
- * Manually execute a single job by its ID.
2137
- * Useful for testing individual job callbacks in isolation.
2138
- * @param {string} id - The job ID to step
2139
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2140
- * @returns {void}
2141
- */
2142
- stepJob(id, timestamp) {
2143
- let job;
2144
- let root;
2145
- for (const r of this.roots.values()) {
2146
- job = r.jobs.get(id);
2147
- if (job) {
2148
- root = r;
2149
- break;
2150
- }
2151
- }
2152
- if (!job || !root) {
2153
- console.warn(`[Scheduler] Job "${id}" not found`);
2154
- return;
2155
- }
2156
- const now = timestamp ?? performance.now();
2157
- const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
2158
- const delta = deltaMs / 1e3;
2159
- const elapsed = now - this.loopState.createdAt;
2160
- const providedState = root.getState?.() ?? {};
2161
- const frameState = {
2162
- ...providedState,
2163
- time: now,
2164
- delta,
2165
- elapsed,
2166
- frame: this.loopState.frameCount
2167
- };
2168
- try {
2169
- job.callback(frameState, delta);
2170
- } catch (error) {
2171
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2172
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2173
- }
2174
- }
2175
- /**
2176
- * Execute a single frame across all roots.
2177
- * Order: globalBefore → each root's jobs → globalAfter
2178
- * @param {number} timestamp - RAF timestamp in milliseconds
2179
- * @returns {void}
2180
- * @private
2181
- */
2182
- executeFrame(timestamp) {
2183
- const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
2184
- const delta = deltaMs / 1e3;
2185
- this.loopState.lastTime = timestamp;
2186
- this.loopState.frameCount++;
2187
- this.loopState.elapsedTime += deltaMs;
2188
- this.runGlobalJobs(this.globalBeforeJobs, timestamp);
2189
- for (const root of this.roots.values()) {
2190
- this.tickRoot(root, timestamp, delta);
2191
- }
2192
- this.runGlobalJobs(this.globalAfterJobs, timestamp);
2193
- }
2194
- /**
2195
- * Run all global jobs from a job map.
2196
- * Catches and logs errors without stopping execution.
2197
- * @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
2198
- * @param {number} timestamp - RAF timestamp in milliseconds
2199
- * @returns {void}
2200
- * @private
2201
- */
2202
- runGlobalJobs(jobs, timestamp) {
2203
- for (const job of jobs.values()) {
2204
- try {
2205
- job.callback(timestamp);
2206
- } catch (error) {
2207
- console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2208
- }
2209
- }
2210
- }
2211
- /**
2212
- * Execute all jobs for a single root in sorted order.
2213
- * Rebuilds sorted job list if needed, then dispatches each job.
2214
- * Errors are caught and propagated via triggerError.
2215
- * @param {RootEntry} root - The root entry to tick
2216
- * @param {number} timestamp - RAF timestamp in milliseconds
2217
- * @param {number} delta - Time since last frame in seconds
2218
- * @returns {void}
2219
- * @private
2220
- */
2221
- tickRoot(root, timestamp, delta) {
2222
- if (root.needsRebuild) {
2223
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2224
- root.needsRebuild = false;
2225
- }
2226
- const providedState = root.getState?.() ?? {};
2227
- const frameState = {
2228
- ...providedState,
2229
- time: timestamp,
2230
- delta,
2231
- elapsed: this.loopState.elapsedTime / 1e3,
2232
- // Convert ms to seconds
2233
- frame: this.loopState.frameCount
2234
- };
2235
- for (const job of root.sortedJobs) {
2236
- if (!shouldRun(job, timestamp)) continue;
2237
- try {
2238
- job.callback(frameState, delta);
2239
- } catch (error) {
2240
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2241
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2242
- }
2243
- }
2244
- }
2245
- //* Debug & Inspection Methods ================================
2246
- /**
2247
- * Get the total number of registered jobs across all roots.
2248
- * Includes both per-root jobs and global before/after jobs.
2249
- * @returns {number} Total job count
2250
- */
2251
- getJobCount() {
2252
- let count = 0;
2253
- for (const root of this.roots.values()) {
2254
- count += root.jobs.size;
2255
- }
2256
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2257
- }
2258
- /**
2259
- * Get all registered job IDs across all roots.
2260
- * Includes both per-root jobs and global before/after jobs.
2261
- * @returns {string[]} Array of all job IDs
2262
- */
2263
- getJobIds() {
2264
- const ids = [];
2265
- for (const root of this.roots.values()) {
2266
- ids.push(...root.jobs.keys());
2267
- }
2268
- ids.push(...this.globalBeforeJobs.keys());
2269
- ids.push(...this.globalAfterJobs.keys());
2270
- return ids;
2271
- }
2272
- /**
2273
- * Get the number of registered roots (Canvas instances).
2274
- * @returns {number} Number of registered roots
2275
- */
2276
- getRootCount() {
2277
- return this.roots.size;
2278
- }
2279
- /**
2280
- * Check if any user (non-system) jobs are registered in a specific phase.
2281
- * Used by the default render job to know if a user has taken over rendering.
2282
- *
2283
- * @param phase The phase to check
2284
- * @param rootId Optional root ID to check (checks all roots if not provided)
2285
- * @returns true if any user jobs exist in the phase
2286
- */
2287
- hasUserJobsInPhase(phase, rootId) {
2288
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2289
- return rootsToCheck.some((root) => {
2290
- if (!root) return false;
2291
- for (const job of root.jobs.values()) {
2292
- if (job.phase === phase && !job.system && job.enabled) return true;
2293
- }
2294
- return false;
2295
- });
2296
- }
2297
- //* Utility Methods ================================
2298
- /**
2299
- * Generate a unique root ID for automatic root registration.
2300
- * @returns {string} A unique root ID in the format 'root_N'
2301
- */
2302
- generateRootId() {
2303
- return `root_${this.nextRootIndex++}`;
2304
- }
2305
- /**
2306
- * Generate a unique job ID.
2307
- * @returns {string} A unique job ID in the format 'job_N'
2308
- * @private
2309
- */
2310
- generateJobId() {
2311
- return `job_${this.nextJobIndex}`;
2312
- }
2313
- /**
2314
- * Normalize before/after constraints to a Set.
2315
- * Handles undefined, single string, or array inputs.
2316
- * @param {string | string[] | undefined} value - The constraint value(s)
2317
- * @returns {Set<string>} Normalized Set of constraint strings
2318
- * @private
2319
- */
2320
- normalizeConstraints(value) {
2321
- if (!value) return /* @__PURE__ */ new Set();
2322
- if (Array.isArray(value)) return new Set(value);
2323
- return /* @__PURE__ */ new Set([value]);
2324
- }
2325
- };
2326
- //* Static State & Methods (Singleton Usage) ================================
2327
- //* Cross-Bundle Singleton Key ==============================
2328
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2329
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2330
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2331
- let Scheduler = _Scheduler;
2332
- const getScheduler = () => Scheduler.get();
2333
- if (hmrData) {
2334
- hmrData.accept?.();
2335
- }
2336
-
2337
- function useFrame(callback, priorityOrOptions) {
2338
- const store = React.useContext(context);
2339
- const isInsideCanvas = store !== null;
2340
- const scheduler = getScheduler();
2341
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2342
- id: priorityOrOptions.id,
2343
- phase: priorityOrOptions.phase,
2344
- priority: priorityOrOptions.priority,
2345
- fps: priorityOrOptions.fps,
2346
- drop: priorityOrOptions.drop,
2347
- enabled: priorityOrOptions.enabled,
2348
- before: priorityOrOptions.before,
2349
- after: priorityOrOptions.after
2350
- }) : "";
2351
- const options = React.useMemo(() => {
2352
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
2353
- }, [optionsKey]);
2354
- const reactId = React.useId();
2355
- const id = options.id ?? reactId;
2356
- const callbackRef = useMutableCallback(callback);
2357
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2358
- useIsomorphicLayoutEffect(() => {
2359
- if (!callback) return;
2360
- if (isInsideCanvas) {
2361
- const state = store.getState();
2362
- const rootId = state.internal.rootId;
2363
- if (isLegacyPriority) {
2364
- state.internal.priority++;
2365
- let parentRoot = state.previousRoot;
2366
- while (parentRoot) {
2367
- const parentState = parentRoot.getState();
2368
- if (parentState?.internal) parentState.internal.priority++;
2369
- parentRoot = parentState?.previousRoot;
2370
- }
2371
- notifyDepreciated({
2372
- heading: "useFrame with numeric priority is deprecated",
2373
- body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
2374
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2375
- });
1971
+ function useFrame(callback, priorityOrOptions) {
1972
+ const store = React.useContext(context);
1973
+ const isInsideCanvas = store !== null;
1974
+ const scheduler = getScheduler();
1975
+ const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1976
+ id: priorityOrOptions.id,
1977
+ phase: priorityOrOptions.phase,
1978
+ priority: priorityOrOptions.priority,
1979
+ fps: priorityOrOptions.fps,
1980
+ drop: priorityOrOptions.drop,
1981
+ enabled: priorityOrOptions.enabled,
1982
+ before: priorityOrOptions.before,
1983
+ after: priorityOrOptions.after
1984
+ }) : "";
1985
+ const options = React.useMemo(() => {
1986
+ return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
1987
+ }, [optionsKey]);
1988
+ const reactId = React.useId();
1989
+ const id = options.id ?? reactId;
1990
+ const callbackRef = useMutableCallback(callback);
1991
+ const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
1992
+ useIsomorphicLayoutEffect(() => {
1993
+ if (!callback) return;
1994
+ if (isInsideCanvas) {
1995
+ const state = store.getState();
1996
+ const rootId = state.internal.rootId;
1997
+ if (isLegacyPriority) {
1998
+ state.internal.priority++;
1999
+ let parentRoot = state.previousRoot;
2000
+ while (parentRoot) {
2001
+ const parentState = parentRoot.getState();
2002
+ if (parentState?.internal) parentState.internal.priority++;
2003
+ parentRoot = parentState?.previousRoot;
2004
+ }
2005
+ notifyDepreciated({
2006
+ heading: "useFrame with numeric priority is deprecated",
2007
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
2008
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2009
+ });
2376
2010
  }
2377
2011
  const wrappedCallback = (frameState, delta) => {
2378
2012
  const localState = store.getState();
@@ -2406,20 +2040,14 @@ function useFrame(callback, priorityOrOptions) {
2406
2040
  }
2407
2041
  };
2408
2042
  } else {
2409
- const registerOutside = () => {
2410
- return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2411
- };
2412
- if (scheduler.independent || scheduler.isReady) {
2413
- return registerOutside();
2414
- }
2415
- let unregisterJob = null;
2416
- const unsubReady = scheduler.onRootReady(() => {
2417
- unregisterJob = registerOutside();
2418
- });
2419
- return () => {
2420
- unsubReady();
2421
- unregisterJob?.();
2422
- };
2043
+ return scheduler.register(
2044
+ (state, delta) => {
2045
+ const frameState = state;
2046
+ if (!frameState.renderer) return;
2047
+ callbackRef.current?.(frameState, delta);
2048
+ },
2049
+ { id, ...options }
2050
+ );
2423
2051
  }
2424
2052
  }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2425
2053
  const isPaused = React.useSyncExternalStore(
@@ -2510,22 +2138,29 @@ function buildFromCache(input, textureCache) {
2510
2138
  function useTexture(input, optionsOrOnLoad) {
2511
2139
  const renderer = useThree((state) => state.internal.actualRenderer);
2512
2140
  const store = useStore();
2513
- const textureCache = useThree((state) => state.textures);
2514
2141
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2515
- const { onLoad, cache = false } = options;
2142
+ const { onLoad, cache = true } = options;
2143
+ const onLoadRef = useRef(onLoad);
2144
+ onLoadRef.current = onLoad;
2145
+ const onLoadCalledForRef = useRef(null);
2516
2146
  const urls = useMemo(() => getUrls(input), [input]);
2517
2147
  const cachedResult = useMemo(() => {
2518
2148
  if (!cache) return null;
2519
- if (!allUrlsCached(urls, textureCache)) return null;
2520
- return buildFromCache(input, textureCache);
2521
- }, [cache, urls, textureCache, input]);
2149
+ const textures = store.getState().textures;
2150
+ if (!allUrlsCached(urls, textures)) return null;
2151
+ return buildFromCache(input, textures);
2152
+ }, [cache, urls, input, store]);
2522
2153
  const loadedTextures = useLoader(
2523
2154
  TextureLoader,
2524
2155
  IsObject(input) ? Object.values(input) : input
2525
2156
  );
2157
+ const inputKey = urls.join("\0");
2526
2158
  useLayoutEffect(() => {
2527
- if (!cachedResult) onLoad?.(loadedTextures);
2528
- }, [onLoad, cachedResult, loadedTextures]);
2159
+ if (cachedResult) return;
2160
+ if (onLoadCalledForRef.current === inputKey) return;
2161
+ onLoadCalledForRef.current = inputKey;
2162
+ onLoadRef.current?.(loadedTextures);
2163
+ }, [cachedResult, loadedTextures, inputKey]);
2529
2164
  useEffect(() => {
2530
2165
  if (cachedResult) return;
2531
2166
  if ("initTexture" in renderer) {
@@ -2558,8 +2193,6 @@ function useTexture(input, optionsOrOnLoad) {
2558
2193
  }, [input, loadedTextures, cachedResult]);
2559
2194
  useEffect(() => {
2560
2195
  if (!cache) return;
2561
- if (cachedResult) return;
2562
- const set = store.setState;
2563
2196
  const urlTextureMap = [];
2564
2197
  if (typeof input === "string") {
2565
2198
  urlTextureMap.push([input, mappedTextures]);
@@ -2573,18 +2206,32 @@ function useTexture(input, optionsOrOnLoad) {
2573
2206
  urlTextureMap.push([url, textureRecord[key]]);
2574
2207
  }
2575
2208
  }
2576
- set((state) => {
2577
- const newMap = new Map(state.textures);
2578
- let changed = false;
2209
+ store.setState((state) => {
2210
+ const refs = new Map(state._textureRefs);
2211
+ let textures = state.textures;
2212
+ let added = false;
2579
2213
  for (const [url, texture] of urlTextureMap) {
2580
- if (!newMap.has(url)) {
2581
- newMap.set(url, texture);
2582
- changed = true;
2214
+ if (!textures.has(url)) {
2215
+ if (!added) {
2216
+ textures = new Map(textures);
2217
+ added = true;
2218
+ }
2219
+ textures.set(url, texture);
2583
2220
  }
2221
+ refs.set(url, (refs.get(url) ?? 0) + 1);
2584
2222
  }
2585
- return changed ? { textures: newMap } : state;
2223
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
2224
+ });
2225
+ return () => store.setState((state) => {
2226
+ const refs = new Map(state._textureRefs);
2227
+ for (const [url] of urlTextureMap) {
2228
+ const next = (refs.get(url) ?? 0) - 1;
2229
+ if (next <= 0) refs.delete(url);
2230
+ else refs.set(url, next);
2231
+ }
2232
+ return { _textureRefs: refs };
2586
2233
  });
2587
- }, [cache, input, mappedTextures, store, cachedResult]);
2234
+ }, [cache, input, mappedTextures, store]);
2588
2235
  return mappedTextures;
2589
2236
  }
2590
2237
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -2600,106 +2247,90 @@ const Texture = ({
2600
2247
  return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
2601
2248
  };
2602
2249
 
2603
- function getTextureValue(entry) {
2604
- if (entry instanceof Texture$1) return entry;
2605
- if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
2606
- return entry.value;
2607
- }
2608
- return null;
2609
- }
2610
- function useTextures() {
2250
+ function useTextures(selector) {
2611
2251
  const store = useStore();
2612
- return useMemo(() => {
2613
- const set = store.setState;
2252
+ const registry = useMemo(() => {
2614
2253
  const getState = store.getState;
2615
- const add = (key, value) => {
2616
- set((state) => {
2617
- const newMap = new Map(state.textures);
2618
- newMap.set(key, value);
2619
- return { textures: newMap };
2620
- });
2621
- };
2622
- const addMultiple = (items) => {
2623
- set((state) => {
2624
- const newMap = new Map(state.textures);
2625
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
2626
- for (const [key, value] of entries) {
2627
- newMap.set(key, value);
2628
- }
2629
- return { textures: newMap };
2630
- });
2631
- };
2632
- const remove = (key) => {
2633
- set((state) => {
2634
- const newMap = new Map(state.textures);
2635
- newMap.delete(key);
2636
- return { textures: newMap };
2637
- });
2638
- };
2639
- const removeMultiple = (keys) => {
2640
- set((state) => {
2641
- const newMap = new Map(state.textures);
2642
- for (const key of keys) newMap.delete(key);
2643
- return { textures: newMap };
2644
- });
2645
- };
2646
- const dispose = (key) => {
2647
- const entry = getState().textures.get(key);
2648
- if (entry) {
2649
- const tex = getTextureValue(entry);
2650
- tex?.dispose();
2651
- }
2652
- remove(key);
2653
- };
2654
- const disposeMultiple = (keys) => {
2655
- const textures = getState().textures;
2656
- for (const key of keys) {
2657
- const entry = textures.get(key);
2658
- if (entry) {
2659
- const tex = getTextureValue(entry);
2660
- tex?.dispose();
2661
- }
2662
- }
2663
- removeMultiple(keys);
2664
- };
2665
- const disposeAll = () => {
2666
- const textures = getState().textures;
2667
- for (const entry of textures.values()) {
2668
- const tex = getTextureValue(entry);
2669
- tex?.dispose();
2670
- }
2671
- set({ textures: /* @__PURE__ */ new Map() });
2672
- };
2254
+ const setState = store.setState;
2255
+ const getOne = (key) => getState().textures.get(key);
2673
2256
  return {
2674
- // Getter for the textures Map (reactive via getState)
2675
- get textures() {
2257
+ get all() {
2676
2258
  return getState().textures;
2677
2259
  },
2678
- // Read
2679
- get: (key) => getState().textures.get(key),
2260
+ get(input) {
2261
+ if (typeof input === "string") return getOne(input);
2262
+ if (Array.isArray(input)) return input.map(getOne);
2263
+ const out = {};
2264
+ for (const name in input) out[name] = getOne(input[name]);
2265
+ return out;
2266
+ },
2680
2267
  has: (key) => getState().textures.has(key),
2681
- // Write
2682
- add,
2683
- addMultiple,
2684
- // Remove (cache only)
2685
- remove,
2686
- removeMultiple,
2687
- // Dispose (GPU + cache)
2688
- dispose,
2689
- disposeMultiple,
2690
- disposeAll
2268
+ add(keyOrRecord, texture) {
2269
+ setState((state) => {
2270
+ const textures = new Map(state.textures);
2271
+ if (typeof keyOrRecord === "string") {
2272
+ textures.set(keyOrRecord, texture);
2273
+ } else {
2274
+ for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
2275
+ }
2276
+ return { textures };
2277
+ });
2278
+ },
2279
+ dispose(key, options) {
2280
+ const state = getState();
2281
+ const refs = state._textureRefs.get(key) ?? 0;
2282
+ if (refs > 0 && !options?.force) {
2283
+ console.warn(
2284
+ `[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
2285
+ );
2286
+ return false;
2287
+ }
2288
+ state.textures.get(key)?.dispose();
2289
+ setState((s) => {
2290
+ const textures = new Map(s.textures);
2291
+ textures.delete(key);
2292
+ const nextRefs = new Map(s._textureRefs);
2293
+ nextRefs.delete(key);
2294
+ return { textures, _textureRefs: nextRefs };
2295
+ });
2296
+ return true;
2297
+ },
2298
+ disposeAll() {
2299
+ for (const texture of getState().textures.values()) texture.dispose();
2300
+ setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
2301
+ }
2691
2302
  };
2692
2303
  }, [store]);
2304
+ const subscribe = selector ? () => selector(registry) : (state) => state.textures;
2305
+ const selected = useThree(subscribe);
2306
+ return selector ? selected : registry;
2693
2307
  }
2694
2308
 
2695
- function useRenderTarget(width, height, options) {
2309
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2696
2310
  const isLegacy = useThree((s) => s.isLegacy);
2697
2311
  const size = useThree((s) => s.size);
2312
+ let width;
2313
+ let height;
2314
+ let opts;
2315
+ if (typeof widthOrOptions === "object") {
2316
+ opts = widthOrOptions;
2317
+ } else if (typeof widthOrOptions === "number") {
2318
+ width = widthOrOptions;
2319
+ if (typeof heightOrOptions === "object") {
2320
+ height = widthOrOptions;
2321
+ opts = heightOrOptions;
2322
+ } else if (typeof heightOrOptions === "number") {
2323
+ height = heightOrOptions;
2324
+ opts = options;
2325
+ } else {
2326
+ height = widthOrOptions;
2327
+ }
2328
+ }
2698
2329
  return useMemo(() => {
2699
2330
  const w = width ?? size.width;
2700
2331
  const h = height ?? size.height;
2701
- return new RenderTarget(w, h, options);
2702
- }, [width, height, size.width, size.height, options, isLegacy]);
2332
+ return new RenderTarget(w, h, opts);
2333
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2703
2334
  }
2704
2335
 
2705
2336
  function useStore() {
@@ -2749,28 +2380,18 @@ function addTail(callback) {
2749
2380
  function invalidate(state, frames = 1, stackFrames = false) {
2750
2381
  getScheduler().invalidate(frames, stackFrames);
2751
2382
  }
2752
- function advance(timestamp, runGlobalEffects = true, state, frame) {
2383
+ function advance(timestamp) {
2753
2384
  getScheduler().step(timestamp);
2754
2385
  }
2755
2386
 
2756
- const version = "10.0.0-alpha.1";
2387
+ const version = "10.0.0-alpha.2";
2757
2388
  const packageData = {
2758
2389
  version: version};
2759
2390
 
2760
2391
  function Xb(Tt) {
2761
2392
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
2762
2393
  }
2763
- var Rm = { exports: {} }, Og = { exports: {} };
2764
- /**
2765
- * @license React
2766
- * react-reconciler.production.js
2767
- *
2768
- * Copyright (c) Meta Platforms, Inc. and affiliates.
2769
- *
2770
- * This source code is licensed under the MIT license found in the
2771
- * LICENSE file in the root directory of this source tree.
2772
- */
2773
- var _b;
2394
+ var Rm = { exports: {} }, Og = { exports: {} }, _b;
2774
2395
  function Kb() {
2775
2396
  return _b || (_b = 1, (function(Tt) {
2776
2397
  Tt.exports = function(m) {
@@ -3842,7 +3463,6 @@ Error generating stack: ` + l.message + `
3842
3463
  if (J === cl || J === jc) throw J;
3843
3464
  var Ge = Yn(29, J, null, P.mode);
3844
3465
  return Ge.lanes = H, Ge.return = P, Ge;
3845
- } finally {
3846
3466
  }
3847
3467
  };
3848
3468
  }
@@ -4496,7 +4116,6 @@ Error generating stack: ` + l.message + `
4496
4116
  var h = r.lastRenderedState, y = d(h, a);
4497
4117
  if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
4498
4118
  } catch {
4499
- } finally {
4500
4119
  }
4501
4120
  if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
4502
4121
  }
@@ -6917,10 +6536,7 @@ Error generating stack: ` + l.message + `
6917
6536
  function vr(t, r) {
6918
6537
  Sf(t, r), (t = t.alternate) && Sf(t, r);
6919
6538
  }
6920
- var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
6921
- var gc = Symbol.for("react.activity");
6922
- var $r = Symbol.for("react.memo_cache_sentinel");
6923
- var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
6539
+ var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
6924
6540
  m.cloneMutableInstance;
6925
6541
  var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
6926
6542
  m.cloneMutableTextInstance;
@@ -7289,17 +6905,7 @@ No matching component was found for:
7289
6905
  }, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
7290
6906
  })(Og)), Og.exports;
7291
6907
  }
7292
- var Mg = { exports: {} };
7293
- /**
7294
- * @license React
7295
- * react-reconciler.development.js
7296
- *
7297
- * Copyright (c) Meta Platforms, Inc. and affiliates.
7298
- *
7299
- * This source code is licensed under the MIT license found in the
7300
- * LICENSE file in the root directory of this source tree.
7301
- */
7302
- var Rb;
6908
+ var Mg = { exports: {} }, Rb;
7303
6909
  function e0() {
7304
6910
  return Rb || (Rb = 1, (function(Tt) {
7305
6911
  process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
@@ -13066,10 +12672,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
13066
12672
  function Ic() {
13067
12673
  return di;
13068
12674
  }
13069
- var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
13070
- var Ds = Symbol.for("react.activity");
13071
- var Bh = Symbol.for("react.memo_cache_sentinel");
13072
- var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
12675
+ var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
13073
12676
  m.cloneMutableInstance;
13074
12677
  var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
13075
12678
  m.cloneMutableTextInstance;
@@ -14037,15 +13640,6 @@ function n0() {
14037
13640
  var t0 = n0();
14038
13641
  const r0 = Xb(t0);
14039
13642
 
14040
- /**
14041
- * @license React
14042
- * react-reconciler-constants.production.js
14043
- *
14044
- * Copyright (c) Meta Platforms, Inc. and affiliates.
14045
- *
14046
- * This source code is licensed under the MIT license found in the
14047
- * LICENSE file in the root directory of this source tree.
14048
- */
14049
13643
  const t = 1, o = 8, r = 32, e = 2;
14050
13644
 
14051
13645
  function createReconciler(config) {
@@ -14072,10 +13666,11 @@ function extend(objects) {
14072
13666
  function validateInstance(type, props) {
14073
13667
  const name = toPascalCase(type);
14074
13668
  const target = catalogue[name];
14075
- if (type !== "primitive" && !target)
13669
+ if (type !== "primitive" && !target) {
14076
13670
  throw new Error(
14077
13671
  `R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
14078
13672
  );
13673
+ }
14079
13674
  if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
14080
13675
  if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
14081
13676
  }
@@ -14239,6 +13834,7 @@ function swapInstances() {
14239
13834
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14240
13835
  instance.object.__r3f = instance;
14241
13836
  setFiberRef(fiber, instance.object);
13837
+ delete instance.appliedOnce;
14242
13838
  applyProps(instance.object, instance.props);
14243
13839
  if (instance.props.attach) {
14244
13840
  attach(parent, instance);
@@ -14312,8 +13908,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14312
13908
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14313
13909
  if (isTailSibling) swapInstances();
14314
13910
  },
14315
- finalizeInitialChildren: () => false,
14316
- commitMount() {
13911
+ finalizeInitialChildren: (instance) => {
13912
+ for (const prop in instance.props) {
13913
+ if (isFromRef(instance.props[prop])) return true;
13914
+ }
13915
+ return false;
13916
+ },
13917
+ commitMount(instance) {
13918
+ const resolved = {};
13919
+ for (const prop in instance.props) {
13920
+ const value = instance.props[prop];
13921
+ if (isFromRef(value)) {
13922
+ const ref = value[FROM_REF];
13923
+ if (ref.current != null) resolved[prop] = ref.current;
13924
+ }
13925
+ }
13926
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14317
13927
  },
14318
13928
  getPublicInstance: (instance) => instance?.object,
14319
13929
  prepareForCommit: () => null,
@@ -14526,14 +14136,17 @@ function createRoot(canvas) {
14526
14136
  if (!prevRoot) _roots.set(canvas, { fiber, store });
14527
14137
  let onCreated;
14528
14138
  let lastCamera;
14529
- let lastConfiguredProps = {};
14139
+ const lastConfiguredProps = {};
14530
14140
  let configured = false;
14531
14141
  let pending = null;
14532
14142
  return {
14533
14143
  async configure(props = {}) {
14534
14144
  let resolve;
14535
14145
  pending = new Promise((_resolve) => resolve = _resolve);
14536
- let {
14146
+ const {
14147
+ id: canvasId,
14148
+ primaryCanvas,
14149
+ scheduler: schedulerConfig,
14537
14150
  gl: glConfig,
14538
14151
  renderer: rendererConfig,
14539
14152
  size: propsSize,
@@ -14541,10 +14154,6 @@ function createRoot(canvas) {
14541
14154
  events,
14542
14155
  onCreated: onCreatedCallback,
14543
14156
  shadows = false,
14544
- linear = false,
14545
- flat = false,
14546
- textureColorSpace = SRGBColorSpace,
14547
- legacy = false,
14548
14157
  orthographic = false,
14549
14158
  frameloop = "always",
14550
14159
  dpr = [1, 2],
@@ -14555,11 +14164,15 @@ function createRoot(canvas) {
14555
14164
  onDragOverMissed,
14556
14165
  onDropMissed,
14557
14166
  autoUpdateFrustum = true,
14558
- occlusion = false
14167
+ occlusion = false,
14168
+ _sizeProps,
14169
+ forceEven
14559
14170
  } = props;
14560
- let state = store.getState();
14171
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
14172
+ const state = store.getState();
14561
14173
  const defaultGPUProps = {
14562
- canvas
14174
+ canvas,
14175
+ antialias: true
14563
14176
  };
14564
14177
  if (glConfig && !R3F_BUILD_LEGACY) {
14565
14178
  throw new Error(
@@ -14570,22 +14183,61 @@ function createRoot(canvas) {
14570
14183
  throw new Error("Cannot use both gl and renderer props at the same time");
14571
14184
  }
14572
14185
  let renderer = state.internal.actualRenderer;
14573
- if (!state.internal.actualRenderer) {
14186
+ if (primaryCanvas && !state.internal.actualRenderer) {
14187
+ const primary = await waitForPrimary(primaryCanvas);
14188
+ renderer = primary.renderer;
14189
+ state.internal.actualRenderer = renderer;
14190
+ const canvasTarget = new CanvasTarget(canvas);
14191
+ primary.store.setState((prev) => ({
14192
+ internal: { ...prev.internal, isMultiCanvas: true }
14193
+ }));
14194
+ state.set((prev) => ({
14195
+ webGPUSupported: primary.store.getState().webGPUSupported,
14196
+ renderer,
14197
+ primaryStore: primary.store,
14198
+ internal: {
14199
+ ...prev.internal,
14200
+ canvasTarget,
14201
+ isMultiCanvas: true,
14202
+ isSecondary: true,
14203
+ targetId: primaryCanvas
14204
+ }
14205
+ }));
14206
+ } else if (!state.internal.actualRenderer) {
14574
14207
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14575
14208
  if (!renderer.hasInitialized?.()) {
14209
+ const size2 = computeInitialSize(canvas, propsSize);
14210
+ if (size2.width > 0 && size2.height > 0) {
14211
+ const pixelRatio = calculateDpr(dpr);
14212
+ canvas.width = size2.width * pixelRatio;
14213
+ canvas.height = size2.height * pixelRatio;
14214
+ }
14576
14215
  await renderer.init();
14577
14216
  }
14578
14217
  const backend = renderer.backend;
14579
14218
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14580
14219
  state.internal.actualRenderer = renderer;
14581
- state.set({ webGPUSupported: isWebGPUBackend, renderer });
14220
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14221
+ if (canvasId && !state.internal.isSecondary) {
14222
+ const canvasTarget = new CanvasTarget(canvas);
14223
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14224
+ state.set((prev) => ({
14225
+ internal: {
14226
+ ...prev.internal,
14227
+ canvasTarget,
14228
+ unregisterPrimary
14229
+ }
14230
+ }));
14231
+ }
14582
14232
  }
14583
14233
  let raycaster = state.raycaster;
14584
14234
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
14585
14235
  const { params, ...options } = raycastOptions || {};
14586
14236
  if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
14587
- if (!is.equ(params, raycaster.params, shallowLoose))
14237
+ if (!is.equ(params, raycaster.params, shallowLoose)) {
14588
14238
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14239
+ }
14240
+ let tempCamera = state.camera;
14589
14241
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14590
14242
  lastCamera = cameraOptions;
14591
14243
  const isCamera = cameraOptions?.isCamera;
@@ -14605,6 +14257,7 @@ function createRoot(canvas) {
14605
14257
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14606
14258
  }
14607
14259
  state.set({ camera });
14260
+ tempCamera = camera;
14608
14261
  raycaster.camera = camera;
14609
14262
  }
14610
14263
  if (!state.scene) {
@@ -14622,7 +14275,7 @@ function createRoot(canvas) {
14622
14275
  rootScene: scene,
14623
14276
  internal: { ...prev.internal, container: scene }
14624
14277
  }));
14625
- const camera = state.camera;
14278
+ const camera = tempCamera;
14626
14279
  if (camera && !camera.parent) scene.add(camera);
14627
14280
  }
14628
14281
  if (events && !state.events.handlers) {
@@ -14636,9 +14289,17 @@ function createRoot(canvas) {
14636
14289
  wasEnabled = enabled;
14637
14290
  });
14638
14291
  }
14292
+ if (_sizeProps !== void 0) {
14293
+ state.set({ _sizeProps });
14294
+ }
14295
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
14296
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
14297
+ }
14639
14298
  const size = computeInitialSize(canvas, propsSize);
14640
- if (!is.equ(size, state.size, shallowLoose)) {
14299
+ if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14300
+ const wasImperative = state._sizeImperative;
14641
14301
  state.setSize(size.width, size.height, size.top, size.left);
14302
+ if (!wasImperative) state.set({ _sizeImperative: false });
14642
14303
  }
14643
14304
  if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
14644
14305
  state.setDpr(dpr);
@@ -14660,10 +14321,10 @@ function createRoot(canvas) {
14660
14321
  lastConfiguredProps.performance = performance;
14661
14322
  }
14662
14323
  if (!state.xr) {
14663
- const handleXRFrame = (timestamp, frame) => {
14324
+ const handleXRFrame = (timestamp, _frame) => {
14664
14325
  const state2 = store.getState();
14665
14326
  if (state2.frameloop === "never") return;
14666
- advance(timestamp, true);
14327
+ advance(timestamp);
14667
14328
  };
14668
14329
  const actualRenderer = state.internal.actualRenderer;
14669
14330
  const handleSessionChange = () => {
@@ -14675,16 +14336,16 @@ function createRoot(canvas) {
14675
14336
  };
14676
14337
  const xr = {
14677
14338
  connect() {
14678
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14679
- const actualRenderer2 = renderer2 || gl;
14680
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14681
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
14339
+ const { gl, renderer: renderer2 } = store.getState();
14340
+ const xrManager = (renderer2 || gl).xr;
14341
+ xrManager.addEventListener("sessionstart", handleSessionChange);
14342
+ xrManager.addEventListener("sessionend", handleSessionChange);
14682
14343
  },
14683
14344
  disconnect() {
14684
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14685
- const actualRenderer2 = renderer2 || gl;
14686
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14687
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
14345
+ const { gl, renderer: renderer2 } = store.getState();
14346
+ const xrManager = (renderer2 || gl).xr;
14347
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
14348
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14688
14349
  }
14689
14350
  };
14690
14351
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14696,41 +14357,92 @@ function createRoot(canvas) {
14696
14357
  const oldType = renderer.shadowMap.type;
14697
14358
  renderer.shadowMap.enabled = !!shadows;
14698
14359
  if (is.boo(shadows)) {
14699
- renderer.shadowMap.type = PCFSoftShadowMap;
14360
+ renderer.shadowMap.type = PCFShadowMap;
14700
14361
  } else if (is.str(shadows)) {
14362
+ if (shadows === "soft") {
14363
+ notifyDepreciated({
14364
+ heading: 'shadows="soft" is deprecated',
14365
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
14366
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
14367
+ });
14368
+ }
14701
14369
  const types = {
14702
14370
  basic: BasicShadowMap,
14703
14371
  percentage: PCFShadowMap,
14704
- soft: PCFSoftShadowMap,
14372
+ soft: PCFShadowMap,
14705
14373
  variance: VSMShadowMap
14706
14374
  };
14707
- renderer.shadowMap.type = types[shadows] ?? PCFSoftShadowMap;
14375
+ renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
14708
14376
  } else if (is.obj(shadows)) {
14709
14377
  Object.assign(renderer.shadowMap, shadows);
14710
14378
  }
14711
- if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
14379
+ if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
14712
14380
  renderer.shadowMap.needsUpdate = true;
14381
+ }
14382
+ }
14383
+ if (!configured) {
14384
+ renderer.outputColorSpace = SRGBColorSpace;
14385
+ renderer.toneMapping = ACESFilmicToneMapping;
14713
14386
  }
14714
14387
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14715
14388
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14716
14389
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14717
14390
  }
14718
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose))
14719
- applyProps(renderer, glConfig);
14391
+ const r3fProps = ["textureColorSpace"];
14392
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
14393
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14394
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14395
+ const glProps = {};
14396
+ for (const key in glConfig) {
14397
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
14398
+ }
14399
+ applyProps(renderer, glProps);
14400
+ }
14720
14401
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14721
14402
  const currentRenderer = state.renderer;
14722
14403
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14723
- applyProps(currentRenderer, rendererConfig);
14404
+ const rendererProps = {};
14405
+ for (const key in rendererConfig) {
14406
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
14407
+ }
14408
+ applyProps(currentRenderer, rendererProps);
14724
14409
  }
14725
14410
  }
14726
14411
  const scheduler = getScheduler();
14727
14412
  const rootId = state.internal.rootId;
14728
14413
  if (!rootId) {
14729
- const newRootId = scheduler.generateRootId();
14414
+ const newRootId = canvasId || scheduler.generateRootId();
14730
14415
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14731
14416
  getState: () => store.getState(),
14732
14417
  onError: (err) => store.getState().setError(err)
14733
14418
  });
14419
+ const unregisterCanvasTarget = scheduler.register(
14420
+ () => {
14421
+ const state2 = store.getState();
14422
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14423
+ const renderer2 = state2.internal.actualRenderer;
14424
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
14425
+ }
14426
+ },
14427
+ {
14428
+ id: `${newRootId}_canvasTarget`,
14429
+ rootId: newRootId,
14430
+ phase: "start",
14431
+ system: true
14432
+ }
14433
+ );
14434
+ const unregisterEventsFlush = scheduler.register(
14435
+ () => {
14436
+ const state2 = store.getState();
14437
+ state2.events.flush?.();
14438
+ },
14439
+ {
14440
+ id: `${newRootId}_events`,
14441
+ rootId: newRootId,
14442
+ phase: "input",
14443
+ system: true
14444
+ }
14445
+ );
14734
14446
  const unregisterFrustum = scheduler.register(
14735
14447
  () => {
14736
14448
  const state2 = store.getState();
@@ -14741,7 +14453,7 @@ function createRoot(canvas) {
14741
14453
  {
14742
14454
  id: `${newRootId}_frustum`,
14743
14455
  rootId: newRootId,
14744
- phase: "preRender",
14456
+ before: "render",
14745
14457
  system: true
14746
14458
  }
14747
14459
  );
@@ -14753,7 +14465,7 @@ function createRoot(canvas) {
14753
14465
  {
14754
14466
  id: `${newRootId}_visibility`,
14755
14467
  rootId: newRootId,
14756
- phase: "preRender",
14468
+ before: "render",
14757
14469
  system: true,
14758
14470
  after: `${newRootId}_frustum`
14759
14471
  }
@@ -14765,18 +14477,22 @@ function createRoot(canvas) {
14765
14477
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14766
14478
  if (userHandlesRender || state2.internal.priority) return;
14767
14479
  try {
14768
- if (state2.postProcessing?.render) state2.postProcessing.render();
14480
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14769
14481
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14770
14482
  } catch (error) {
14771
14483
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14772
14484
  }
14773
14485
  },
14774
14486
  {
14775
- id: `${newRootId}_render`,
14487
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
14488
+ id: canvasId || `${newRootId}_render`,
14776
14489
  rootId: newRootId,
14777
14490
  phase: "render",
14778
- system: true
14491
+ system: true,
14779
14492
  // Internal flag: this is a system job, not user-controlled
14493
+ // Apply scheduler config for render ordering and rate limiting
14494
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
14495
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14780
14496
  }
14781
14497
  );
14782
14498
  state.set((state2) => ({
@@ -14785,6 +14501,8 @@ function createRoot(canvas) {
14785
14501
  rootId: newRootId,
14786
14502
  unregisterRoot: () => {
14787
14503
  unregisterRoot();
14504
+ unregisterCanvasTarget();
14505
+ unregisterEventsFlush();
14788
14506
  unregisterFrustum();
14789
14507
  unregisterVisibility();
14790
14508
  unregisterRender();
@@ -14843,15 +14561,24 @@ function unmountComponentAtNode(canvas, callback) {
14843
14561
  const renderer = state.internal.actualRenderer;
14844
14562
  const unregisterRoot = state.internal.unregisterRoot;
14845
14563
  if (unregisterRoot) unregisterRoot();
14564
+ const unregisterPrimary = state.internal.unregisterPrimary;
14565
+ if (unregisterPrimary) unregisterPrimary();
14566
+ const canvasTarget = state.internal.canvasTarget;
14567
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14846
14568
  state.events.disconnect?.();
14847
14569
  cleanupHelperGroup(root.store);
14848
- renderer?.renderLists?.dispose?.();
14849
- renderer?.forceContextLoss?.();
14850
- if (renderer?.xr) state.xr.disconnect();
14570
+ if (state.isLegacy && renderer) {
14571
+ ;
14572
+ renderer.renderLists?.dispose?.();
14573
+ renderer.forceContextLoss?.();
14574
+ }
14575
+ if (!state.internal.isSecondary) {
14576
+ if (renderer?.xr) state.xr.disconnect();
14577
+ }
14851
14578
  dispose(state.scene);
14852
14579
  _roots.delete(canvas);
14853
14580
  if (callback) callback(canvas);
14854
- } catch (e) {
14581
+ } catch {
14855
14582
  }
14856
14583
  }, 500);
14857
14584
  }
@@ -14859,36 +14586,34 @@ function unmountComponentAtNode(canvas, callback) {
14859
14586
  }
14860
14587
  }
14861
14588
  function createPortal(children, container, state) {
14862
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
14589
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14863
14590
  }
14864
- function PortalWrapper({ children, container, state }) {
14591
+ function Portal({ children, container, state }) {
14865
14592
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14866
- const [resolvedContainer, setResolvedContainer] = useState(() => {
14593
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14867
14594
  if (isRef(container)) return container.current ?? null;
14868
14595
  return container;
14869
14596
  });
14597
+ const setResolvedContainer = useCallback(
14598
+ (newContainer) => {
14599
+ if (!newContainer || newContainer === resolvedContainer) return;
14600
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
14601
+ },
14602
+ [resolvedContainer, _setResolvedContainer, isRef]
14603
+ );
14870
14604
  useMemo(() => {
14871
- if (isRef(container)) {
14872
- const current = container.current;
14873
- if (!current) {
14874
- queueMicrotask(() => {
14875
- const updated = container.current;
14876
- if (updated && updated !== resolvedContainer) {
14877
- setResolvedContainer(updated);
14878
- }
14879
- });
14880
- } else if (current !== resolvedContainer) {
14881
- setResolvedContainer(current);
14882
- }
14883
- } else if (container !== resolvedContainer) {
14884
- setResolvedContainer(container);
14605
+ if (isRef(container) && !container.current) {
14606
+ return queueMicrotask(() => {
14607
+ setResolvedContainer(container.current);
14608
+ });
14885
14609
  }
14886
- }, [container, resolvedContainer, isRef]);
14610
+ setResolvedContainer(container);
14611
+ }, [container, isRef, setResolvedContainer]);
14887
14612
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14888
14613
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14889
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
14614
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14890
14615
  }
14891
- function Portal({ state = {}, children, container }) {
14616
+ function PortalInner({ state = {}, children, container }) {
14892
14617
  const { events, size, injectScene = true, ...rest } = state;
14893
14618
  const previousRoot = useStore();
14894
14619
  const [raycaster] = useState(() => new Raycaster());
@@ -14909,11 +14634,12 @@ function Portal({ state = {}, children, container }) {
14909
14634
  };
14910
14635
  }, [portalScene, container, injectScene]);
14911
14636
  const inject = useMutableCallback((rootState, injectState) => {
14637
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14912
14638
  let viewport = void 0;
14913
- if (injectState.camera && size) {
14639
+ if (injectState.camera && (size || injectState.size)) {
14914
14640
  const camera = injectState.camera;
14915
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14916
- if (camera !== rootState.camera) updateCamera(camera, size);
14641
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
14642
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14917
14643
  }
14918
14644
  return {
14919
14645
  // The intersect consists of the previous root state
@@ -14930,7 +14656,7 @@ function Portal({ state = {}, children, container }) {
14930
14656
  previousRoot,
14931
14657
  // Events, size and viewport can be overridden by the inject layer
14932
14658
  events: { ...rootState.events, ...injectState.events, ...events },
14933
- size: { ...rootState.size, ...size },
14659
+ size: resolvedSize,
14934
14660
  viewport: { ...rootState.viewport, ...viewport },
14935
14661
  // Layers are allowed to override events
14936
14662
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14942,9 +14668,13 @@ function Portal({ state = {}, children, container }) {
14942
14668
  const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
14943
14669
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
14944
14670
  onMutate(previousRoot.getState());
14945
- previousRoot.subscribe(onMutate);
14946
14671
  return store;
14947
14672
  }, [previousRoot, container]);
14673
+ useIsomorphicLayoutEffect(() => {
14674
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
14675
+ const unsubscribe = previousRoot.subscribe(onMutate);
14676
+ return unsubscribe;
14677
+ }, [previousRoot, usePortalStore]);
14948
14678
  return (
14949
14679
  // @ts-ignore, reconciler types are not maintained
14950
14680
  /* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
@@ -14958,21 +14688,59 @@ function flushSync(fn) {
14958
14688
  return reconciler.flushSyncFromReconciler(fn);
14959
14689
  }
14960
14690
 
14691
+ function parseBackground(background) {
14692
+ if (!background) return null;
14693
+ if (typeof background === "object" && !background.isColor) {
14694
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
14695
+ return {
14696
+ ...rest,
14697
+ preset,
14698
+ files: envMap || files,
14699
+ backgroundFiles: backgroundMap,
14700
+ background: true
14701
+ };
14702
+ }
14703
+ if (typeof background === "number") {
14704
+ return { color: background, background: true };
14705
+ }
14706
+ if (typeof background === "string") {
14707
+ if (background in presetsObj) {
14708
+ return { preset: background, background: true };
14709
+ }
14710
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
14711
+ return { files: background, background: true };
14712
+ }
14713
+ return { color: background, background: true };
14714
+ }
14715
+ if (background.isColor) {
14716
+ return { color: background, background: true };
14717
+ }
14718
+ return null;
14719
+ }
14720
+
14721
+ function clearHmrCaches(store) {
14722
+ store.setState((state) => ({
14723
+ nodes: {},
14724
+ uniforms: {},
14725
+ buffers: {},
14726
+ gpuStorage: {},
14727
+ _hmrVersion: state._hmrVersion + 1
14728
+ }));
14729
+ }
14730
+
14961
14731
  function CanvasImpl({
14962
14732
  ref,
14963
14733
  children,
14964
14734
  fallback,
14965
14735
  resize,
14966
14736
  style,
14737
+ id,
14967
14738
  gl,
14968
- renderer,
14739
+ renderer: rendererProp,
14969
14740
  events = createPointerEvents,
14970
14741
  eventSource,
14971
14742
  eventPrefix,
14972
14743
  shadows,
14973
- linear,
14974
- flat,
14975
- legacy,
14976
14744
  orthographic,
14977
14745
  frameloop,
14978
14746
  dpr,
@@ -14980,14 +14748,34 @@ function CanvasImpl({
14980
14748
  raycaster,
14981
14749
  camera,
14982
14750
  scene,
14751
+ autoUpdateFrustum,
14752
+ occlusion,
14983
14753
  onPointerMissed,
14984
14754
  onDragOverMissed,
14985
14755
  onDropMissed,
14986
14756
  onCreated,
14757
+ hmr,
14758
+ width,
14759
+ height,
14760
+ background,
14761
+ forceEven,
14987
14762
  ...props
14988
14763
  }) {
14764
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14765
+ let primaryCanvas;
14766
+ let scheduler;
14767
+ let renderer;
14768
+ if (isRendererConfig) {
14769
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14770
+ primaryCanvas = pc;
14771
+ scheduler = sc;
14772
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14773
+ } else {
14774
+ renderer = rendererProp;
14775
+ }
14989
14776
  React.useMemo(() => extend(THREE), []);
14990
14777
  const Bridge = useBridge();
14778
+ const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
14991
14779
  const hasInitialSizeRef = React.useRef(false);
14992
14780
  const measureConfig = React.useMemo(() => {
14993
14781
  if (!hasInitialSizeRef.current) {
@@ -15004,7 +14792,21 @@ function CanvasImpl({
15004
14792
  };
15005
14793
  }, [resize, hasInitialSizeRef.current]);
15006
14794
  const [containerRef, containerRect] = useMeasure(measureConfig);
15007
- if (!hasInitialSizeRef.current && containerRect.width > 0 && containerRect.height > 0) {
14795
+ const effectiveSize = React.useMemo(() => {
14796
+ let w = width ?? containerRect.width;
14797
+ let h = height ?? containerRect.height;
14798
+ if (forceEven) {
14799
+ w = Math.ceil(w / 2) * 2;
14800
+ h = Math.ceil(h / 2) * 2;
14801
+ }
14802
+ return {
14803
+ width: w,
14804
+ height: h,
14805
+ top: containerRect.top,
14806
+ left: containerRect.left
14807
+ };
14808
+ }, [width, height, containerRect, forceEven]);
14809
+ if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15008
14810
  hasInitialSizeRef.current = true;
15009
14811
  }
15010
14812
  const canvasRef = React.useRef(null);
@@ -15023,7 +14825,7 @@ function CanvasImpl({
15023
14825
  useIsomorphicLayoutEffect(() => {
15024
14826
  effectActiveRef.current = true;
15025
14827
  const canvas = canvasRef.current;
15026
- if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
14828
+ if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
15027
14829
  if (!root.current) {
15028
14830
  root.current = createRoot(canvas);
15029
14831
  notifyAlpha({
@@ -15043,21 +14845,26 @@ function CanvasImpl({
15043
14845
  async function run() {
15044
14846
  if (!effectActiveRef.current || !root.current) return;
15045
14847
  await root.current.configure({
14848
+ id,
14849
+ primaryCanvas,
14850
+ scheduler,
15046
14851
  gl,
15047
14852
  renderer,
15048
14853
  scene,
15049
14854
  events,
15050
14855
  shadows,
15051
- linear,
15052
- flat,
15053
- legacy,
15054
14856
  orthographic,
15055
14857
  frameloop,
15056
14858
  dpr,
15057
14859
  performance,
15058
14860
  raycaster,
15059
14861
  camera,
15060
- size: containerRect,
14862
+ autoUpdateFrustum,
14863
+ occlusion,
14864
+ size: effectiveSize,
14865
+ // Store size props for reset functionality
14866
+ _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
14867
+ forceEven,
15061
14868
  // Pass mutable reference to onPointerMissed so it's free to update
15062
14869
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15063
14870
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15081,7 +14888,10 @@ function CanvasImpl({
15081
14888
  });
15082
14889
  if (!effectActiveRef.current || !root.current) return;
15083
14890
  root.current.render(
15084
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
14891
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
14892
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
14893
+ children ?? null
14894
+ ] }) }) })
15085
14895
  );
15086
14896
  }
15087
14897
  run();
@@ -15102,7 +14912,29 @@ function CanvasImpl({
15102
14912
  root.current = null;
15103
14913
  };
15104
14914
  }
15105
- }, []);
14915
+ }, []);
14916
+ React.useEffect(() => {
14917
+ if (hmr === false) return;
14918
+ const canvas = canvasRef.current;
14919
+ if (!canvas) return;
14920
+ const handleHMR = () => {
14921
+ queueMicrotask(() => {
14922
+ const rootEntry = _roots.get(canvas);
14923
+ if (rootEntry?.store) clearHmrCaches(rootEntry.store);
14924
+ });
14925
+ };
14926
+ if (typeof import.meta !== "undefined" && import.meta.hot) {
14927
+ const hot = import.meta.hot;
14928
+ hot.on("vite:afterUpdate", handleHMR);
14929
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
14930
+ }
14931
+ if (typeof module !== "undefined" && module.hot) {
14932
+ const hot = module.hot;
14933
+ hot.addStatusHandler((status) => {
14934
+ if (status === "idle") handleHMR();
14935
+ });
14936
+ }
14937
+ }, [hmr]);
15106
14938
  const pointerEvents = eventSource ? "none" : "auto";
15107
14939
  return /* @__PURE__ */ jsx(
15108
14940
  "div",
@@ -15117,7 +14949,16 @@ function CanvasImpl({
15117
14949
  ...style
15118
14950
  },
15119
14951
  ...props,
15120
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
14952
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
14953
+ "canvas",
14954
+ {
14955
+ ref: canvasRef,
14956
+ id,
14957
+ className: "r3f-canvas",
14958
+ style: { display: "block", width: "100%", height: "100%" },
14959
+ children: fallback
14960
+ }
14961
+ ) })
15121
14962
  }
15122
14963
  );
15123
14964
  }
@@ -15125,6 +14966,114 @@ function Canvas(props) {
15125
14966
  return /* @__PURE__ */ jsx(FiberProvider, { children: /* @__PURE__ */ jsx(CanvasImpl, { ...props }) });
15126
14967
  }
15127
14968
 
14969
+ var __defProp = Object.defineProperty;
14970
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14971
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
14972
+ var _a;
14973
+ const INTERNAL_DATA = Symbol("ScopedStore.data");
14974
+ _a = INTERNAL_DATA;
14975
+ const _ScopedStore = class _ScopedStore {
14976
+ constructor(data) {
14977
+ __publicField(this, _a);
14978
+ this[INTERNAL_DATA] = data;
14979
+ return new Proxy(this, {
14980
+ get(target, prop, receiver) {
14981
+ if (typeof prop === "string") {
14982
+ if (prop === "scope" || prop === "has" || prop === "keys") {
14983
+ return Reflect.get(target, prop, receiver);
14984
+ }
14985
+ return target[INTERNAL_DATA][prop];
14986
+ }
14987
+ return Reflect.get(target, prop, receiver);
14988
+ },
14989
+ has(target, prop) {
14990
+ return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
14991
+ },
14992
+ ownKeys(target) {
14993
+ return Reflect.ownKeys(target[INTERNAL_DATA]);
14994
+ },
14995
+ getOwnPropertyDescriptor(target, prop) {
14996
+ if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
14997
+ return {
14998
+ configurable: true,
14999
+ enumerable: true,
15000
+ value: target[INTERNAL_DATA][prop]
15001
+ };
15002
+ }
15003
+ return void 0;
15004
+ }
15005
+ });
15006
+ }
15007
+ /**
15008
+ * Access a nested scope by key.
15009
+ * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15010
+ */
15011
+ scope(key) {
15012
+ const data = this[INTERNAL_DATA][key];
15013
+ return new _ScopedStore(
15014
+ data && typeof data === "object" ? data : {}
15015
+ );
15016
+ }
15017
+ /**
15018
+ * Check if a key exists in the store.
15019
+ */
15020
+ has(key) {
15021
+ return key in this[INTERNAL_DATA];
15022
+ }
15023
+ /**
15024
+ * Get all keys in the store.
15025
+ */
15026
+ keys() {
15027
+ return Object.keys(this[INTERNAL_DATA]);
15028
+ }
15029
+ };
15030
+ let ScopedStore = _ScopedStore;
15031
+ function createScopedStore(data) {
15032
+ return new ScopedStore(data);
15033
+ }
15034
+ function createLazyCreatorState(state) {
15035
+ let _uniforms = null;
15036
+ let _nodes = null;
15037
+ let _buffers = null;
15038
+ let _gpuStorage = null;
15039
+ return Object.create(state, {
15040
+ uniforms: {
15041
+ get() {
15042
+ return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
15043
+ }
15044
+ },
15045
+ nodes: {
15046
+ get() {
15047
+ return _nodes ?? (_nodes = createScopedStore(state.nodes));
15048
+ }
15049
+ },
15050
+ buffers: {
15051
+ get() {
15052
+ return _buffers ?? (_buffers = createScopedStore(state.buffers));
15053
+ }
15054
+ },
15055
+ gpuStorage: {
15056
+ get() {
15057
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
15058
+ }
15059
+ }
15060
+ });
15061
+ }
15062
+
15063
+ function resolvePrimary(local) {
15064
+ return local.getState().primaryStore ?? local;
15065
+ }
15066
+ function usePrimaryStore() {
15067
+ const local = useStore();
15068
+ const primary = local.getState().primaryStore;
15069
+ return useMemo(() => primary ?? local, [primary, local]);
15070
+ }
15071
+ function usePrimaryThree(selector = (state) => state, equalityFn) {
15072
+ const local = useStore();
15073
+ const primary = resolvePrimary(local);
15074
+ return primary(selector, equalityFn);
15075
+ }
15076
+
15128
15077
  function addTexture(set, key, value) {
15129
15078
  set((state) => {
15130
15079
  const newMap = new Map(state.textures);
@@ -15164,6 +15113,27 @@ function createTextureOperations(set) {
15164
15113
  removeMultiple: (keys) => removeTextures(set, keys)
15165
15114
  };
15166
15115
  }
15116
+ function extractTSLValue(value) {
15117
+ if (value === null || value === void 0) return value;
15118
+ if (typeof value !== "object") return value;
15119
+ const node = value;
15120
+ if (!node.isNode) return value;
15121
+ if (node.isConstNode) {
15122
+ return node.value;
15123
+ }
15124
+ if ("value" in node) {
15125
+ let extractedValue = node.value;
15126
+ if (typeof node.traverse === "function") {
15127
+ node.traverse((n) => {
15128
+ if (n.isConstNode) {
15129
+ extractedValue = n.value;
15130
+ }
15131
+ });
15132
+ }
15133
+ return extractedValue;
15134
+ }
15135
+ return value;
15136
+ }
15167
15137
  function vectorize(inObject) {
15168
15138
  if (inObject === null || inObject === void 0) return inObject;
15169
15139
  if (typeof inObject === "string") {
@@ -15176,9 +15146,16 @@ function vectorize(inObject) {
15176
15146
  }
15177
15147
  if (typeof inObject !== "object") return inObject;
15178
15148
  const obj = inObject;
15149
+ if (obj.isNode) {
15150
+ return extractTSLValue(inObject);
15151
+ }
15179
15152
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15180
15153
  if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15181
15154
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15155
+ if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15156
+ const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
15157
+ return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
15158
+ }
15182
15159
  if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
15183
15160
  if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
15184
15161
  return new Vector4(obj.x, obj.y, obj.z, obj.w);
@@ -15202,7 +15179,7 @@ function useCompareMemoize(value, deep) {
15202
15179
 
15203
15180
  const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15204
15181
  function useUniforms(creatorOrScope, scope) {
15205
- const store = useStore();
15182
+ const store = usePrimaryStore();
15206
15183
  const removeUniforms2 = useCallback(
15207
15184
  (names, targetScope) => {
15208
15185
  const nameArray = Array.isArray(names) ? names : [names];
@@ -15238,21 +15215,55 @@ function useUniforms(creatorOrScope, scope) {
15238
15215
  },
15239
15216
  [store]
15240
15217
  );
15218
+ const rebuildUniforms = useCallback(
15219
+ (targetScope) => {
15220
+ store.setState((state) => {
15221
+ let newUniforms = {};
15222
+ if (targetScope && targetScope !== "root") {
15223
+ const { [targetScope]: _, ...rest } = state.uniforms;
15224
+ newUniforms = rest;
15225
+ } else if (targetScope === "root") {
15226
+ for (const [key, value] of Object.entries(state.uniforms)) {
15227
+ if (!isUniformNode$1(value)) newUniforms[key] = value;
15228
+ }
15229
+ }
15230
+ return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15231
+ });
15232
+ },
15233
+ [store]
15234
+ );
15241
15235
  const inputForMemoization = useMemo(() => {
15242
- return is.fun(creatorOrScope) ? creatorOrScope(store.getState()) : creatorOrScope;
15236
+ let raw = creatorOrScope;
15237
+ if (is.fun(creatorOrScope)) {
15238
+ const wrappedState = createLazyCreatorState(store.getState());
15239
+ raw = creatorOrScope(wrappedState);
15240
+ }
15241
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
15242
+ const normalized = {};
15243
+ for (const [key, value] of Object.entries(raw)) {
15244
+ normalized[key] = vectorize(value);
15245
+ }
15246
+ return normalized;
15247
+ }
15248
+ return raw;
15243
15249
  }, [creatorOrScope, store]);
15244
15250
  const memoizedInput = useCompareMemoize(inputForMemoization);
15251
+ const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15252
+ const storeUniforms = usePrimaryThree((s) => s.uniforms);
15253
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15254
+ const readerDep = isReader ? storeUniforms : null;
15255
+ const creatorDep = isReader ? null : hmrVersion;
15245
15256
  const uniforms = useMemo(() => {
15246
- const state = store.getState();
15247
- const set = store.setState;
15248
15257
  if (memoizedInput === void 0) {
15249
- return state.uniforms;
15258
+ return storeUniforms;
15250
15259
  }
15251
15260
  if (typeof memoizedInput === "string") {
15252
- const scopeData = state.uniforms[memoizedInput];
15261
+ const scopeData = storeUniforms[memoizedInput];
15253
15262
  if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
15254
15263
  return {};
15255
15264
  }
15265
+ const state = store.getState();
15266
+ const set = store.setState;
15256
15267
  if (typeof memoizedInput !== "object" || memoizedInput === null) {
15257
15268
  throw new Error("Invalid uniform input");
15258
15269
  }
@@ -15296,8 +15307,23 @@ function useUniforms(creatorOrScope, scope) {
15296
15307
  }
15297
15308
  }
15298
15309
  return result;
15299
- }, [store, memoizedInput, scope]);
15300
- return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
15310
+ }, [store, memoizedInput, scope, readerDep, creatorDep]);
15311
+ return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15312
+ }
15313
+ function rebuildAllUniforms(store, scope) {
15314
+ store = store.getState().primaryStore ?? store;
15315
+ store.setState((state) => {
15316
+ let newUniforms = {};
15317
+ if (scope && scope !== "root") {
15318
+ const { [scope]: _, ...rest } = state.uniforms;
15319
+ newUniforms = rest;
15320
+ } else if (scope === "root") {
15321
+ for (const [key, value] of Object.entries(state.uniforms)) {
15322
+ if (!isUniformNode$1(value)) newUniforms[key] = value;
15323
+ }
15324
+ }
15325
+ return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15326
+ });
15301
15327
  }
15302
15328
  function removeUniforms(set, names, scope) {
15303
15329
  set((state) => {
@@ -15362,15 +15388,17 @@ function isSameThreeType(a, b) {
15362
15388
  }
15363
15389
 
15364
15390
  const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15391
+ const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15365
15392
  function useUniform(name, value) {
15366
15393
  const store = useStore();
15394
+ const hmrVersion = useThree((s) => s._hmrVersion);
15367
15395
  return useMemo(() => {
15368
15396
  const state = store.getState();
15369
15397
  const set = store.setState;
15370
15398
  const existing = state.uniforms[name];
15371
15399
  if (existing && isUniformNode(existing)) {
15372
- if (value !== void 0) {
15373
- existing.value = value;
15400
+ if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15401
+ existing.value = typeof value === "string" ? new Color(value) : value;
15374
15402
  }
15375
15403
  return existing;
15376
15404
  }
@@ -15379,7 +15407,24 @@ function useUniform(name, value) {
15379
15407
  `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15380
15408
  );
15381
15409
  }
15382
- const node = uniform(value);
15410
+ if (isUniformNode(value)) {
15411
+ const node2 = value;
15412
+ if (typeof node2.setName === "function") {
15413
+ node2.setName(name);
15414
+ }
15415
+ set((s) => ({
15416
+ uniforms: { ...s.uniforms, [name]: node2 }
15417
+ }));
15418
+ return node2;
15419
+ }
15420
+ let node;
15421
+ if (isTSLNode$1(value)) {
15422
+ node = uniform(value);
15423
+ } else if (typeof value === "string") {
15424
+ node = uniform(new Color(value));
15425
+ } else {
15426
+ node = uniform(value);
15427
+ }
15383
15428
  if (typeof node.setName === "function") {
15384
15429
  node.setName(name);
15385
15430
  }
@@ -15390,12 +15435,12 @@ function useUniform(name, value) {
15390
15435
  }
15391
15436
  }));
15392
15437
  return node;
15393
- }, [store, name]);
15438
+ }, [store, name, hmrVersion]);
15394
15439
  }
15395
15440
 
15396
15441
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15397
15442
  function useNodes(creatorOrScope, scope) {
15398
- const store = useStore();
15443
+ const store = usePrimaryStore();
15399
15444
  const removeNodes2 = useCallback(
15400
15445
  (names, targetScope) => {
15401
15446
  const nameArray = Array.isArray(names) ? names : [names];
@@ -15431,19 +15476,46 @@ function useNodes(creatorOrScope, scope) {
15431
15476
  },
15432
15477
  [store]
15433
15478
  );
15479
+ const rebuildNodes = useCallback(
15480
+ (targetScope) => {
15481
+ store.setState((state) => {
15482
+ let newNodes = state.nodes;
15483
+ if (targetScope && targetScope !== "root") {
15484
+ const { [targetScope]: _, ...rest } = state.nodes;
15485
+ newNodes = rest;
15486
+ } else if (targetScope === "root") {
15487
+ newNodes = {};
15488
+ for (const [key, value] of Object.entries(state.nodes)) {
15489
+ if (!isTSLNode(value)) newNodes[key] = value;
15490
+ }
15491
+ } else {
15492
+ newNodes = {};
15493
+ }
15494
+ return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15495
+ });
15496
+ },
15497
+ [store]
15498
+ );
15499
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15500
+ const storeNodes = usePrimaryThree((s) => s.nodes);
15501
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15502
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15503
+ const readerDep = isReader ? storeNodes : null;
15504
+ const creatorDep = isReader ? null : hmrVersion;
15434
15505
  const nodes = useMemo(() => {
15435
- const state = store.getState();
15436
- const set = store.setState;
15437
15506
  if (creatorOrScope === void 0) {
15438
- return state.nodes;
15507
+ return storeNodes;
15439
15508
  }
15440
15509
  if (typeof creatorOrScope === "string") {
15441
- const scopeData = state.nodes[creatorOrScope];
15510
+ const scopeData = storeNodes[creatorOrScope];
15442
15511
  if (scopeData && !isTSLNode(scopeData)) return scopeData;
15443
15512
  return {};
15444
15513
  }
15514
+ const state = store.getState();
15515
+ const set = store.setState;
15445
15516
  const creator = creatorOrScope;
15446
- const created = creator(state);
15517
+ const wrappedState = createLazyCreatorState(state);
15518
+ const created = creator(wrappedState);
15447
15519
  const result = {};
15448
15520
  let hasNewNodes = false;
15449
15521
  if (scope) {
@@ -15452,7 +15524,7 @@ function useNodes(creatorOrScope, scope) {
15452
15524
  if (currentScope[name]) {
15453
15525
  result[name] = currentScope[name];
15454
15526
  } else {
15455
- if (typeof node.label === "function") node.setName(`${scope}.${name}`);
15527
+ node.setName?.(`${scope}.${name}`);
15456
15528
  result[name] = node;
15457
15529
  hasNewNodes = true;
15458
15530
  }
@@ -15472,7 +15544,7 @@ function useNodes(creatorOrScope, scope) {
15472
15544
  if (existing && isTSLNode(existing)) {
15473
15545
  result[name] = existing;
15474
15546
  } else {
15475
- if (typeof node.label === "function") node.setName(name);
15547
+ node.setName?.(name);
15476
15548
  result[name] = node;
15477
15549
  hasNewNodes = true;
15478
15550
  }
@@ -15481,8 +15553,26 @@ function useNodes(creatorOrScope, scope) {
15481
15553
  set((s) => ({ nodes: { ...s.nodes, ...result } }));
15482
15554
  }
15483
15555
  return result;
15484
- }, [store, typeof creatorOrScope === "string" ? creatorOrScope : scope]);
15485
- return { ...nodes, removeNodes: removeNodes2, clearNodes };
15556
+ }, [store, scopeDep, readerDep, creatorDep]);
15557
+ return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
15558
+ }
15559
+ function rebuildAllNodes(store, scope) {
15560
+ store = store.getState().primaryStore ?? store;
15561
+ store.setState((state) => {
15562
+ let newNodes = state.nodes;
15563
+ if (scope && scope !== "root") {
15564
+ const { [scope]: _, ...rest } = state.nodes;
15565
+ newNodes = rest;
15566
+ } else if (scope === "root") {
15567
+ newNodes = {};
15568
+ for (const [key, value] of Object.entries(state.nodes)) {
15569
+ if (!isTSLNode(value)) newNodes[key] = value;
15570
+ }
15571
+ } else {
15572
+ newNodes = {};
15573
+ }
15574
+ return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15575
+ });
15486
15576
  }
15487
15577
  function removeNodes(set, names, scope) {
15488
15578
  set((state) => {
@@ -15512,17 +15602,364 @@ function clearRootNodes(set) {
15512
15602
  });
15513
15603
  }
15514
15604
  function useLocalNodes(creator) {
15515
- const store = useStore();
15516
- const uniforms = useThree((s) => s.uniforms);
15517
- const nodes = useThree((s) => s.nodes);
15518
- const textures = useThree((s) => s.textures);
15605
+ const store = usePrimaryStore();
15606
+ const uniforms = usePrimaryThree((s) => s.uniforms);
15607
+ const nodes = usePrimaryThree((s) => s.nodes);
15608
+ const textures = usePrimaryThree((s) => s.textures);
15609
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15519
15610
  return useMemo(() => {
15611
+ const wrappedState = createLazyCreatorState(store.getState());
15612
+ return creator(wrappedState);
15613
+ }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15614
+ }
15615
+
15616
+ const isBufferLike = (value) => {
15617
+ if (value === null || typeof value !== "object") return false;
15618
+ if (ArrayBuffer.isView(value)) return true;
15619
+ if ("isBufferAttribute" in value) return true;
15620
+ if ("uuid" in value || "nodeType" in value) return true;
15621
+ return false;
15622
+ };
15623
+ const disposeBuffer = (buffer) => {
15624
+ if (buffer === null || typeof buffer !== "object") return;
15625
+ if ("dispose" in buffer && typeof buffer.dispose === "function") {
15626
+ buffer.dispose();
15627
+ }
15628
+ };
15629
+ function useBuffers(creatorOrScope, scope) {
15630
+ const store = usePrimaryStore();
15631
+ const removeBuffers = useCallback(
15632
+ (names, targetScope) => {
15633
+ const nameArray = Array.isArray(names) ? names : [names];
15634
+ store.setState((state) => {
15635
+ if (targetScope) {
15636
+ const currentScope = { ...state.buffers[targetScope] };
15637
+ for (const name of nameArray) delete currentScope[name];
15638
+ return { buffers: { ...state.buffers, [targetScope]: currentScope } };
15639
+ }
15640
+ const buffers2 = { ...state.buffers };
15641
+ for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
15642
+ return { buffers: buffers2 };
15643
+ });
15644
+ },
15645
+ [store]
15646
+ );
15647
+ const clearBuffers = useCallback(
15648
+ (targetScope) => {
15649
+ store.setState((state) => {
15650
+ if (targetScope && targetScope !== "root") {
15651
+ const { [targetScope]: _, ...rest } = state.buffers;
15652
+ return { buffers: rest };
15653
+ }
15654
+ if (targetScope === "root") {
15655
+ const buffers2 = {};
15656
+ for (const [key, value] of Object.entries(state.buffers)) {
15657
+ if (!isBufferLike(value)) buffers2[key] = value;
15658
+ }
15659
+ return { buffers: buffers2 };
15660
+ }
15661
+ return { buffers: {} };
15662
+ });
15663
+ },
15664
+ [store]
15665
+ );
15666
+ const rebuildBuffers = useCallback(
15667
+ (targetScope) => {
15668
+ store.setState((state) => {
15669
+ let newBuffers = state.buffers;
15670
+ if (targetScope && targetScope !== "root") {
15671
+ const { [targetScope]: _, ...rest } = state.buffers;
15672
+ newBuffers = rest;
15673
+ } else if (targetScope === "root") {
15674
+ newBuffers = {};
15675
+ for (const [key, value] of Object.entries(state.buffers)) {
15676
+ if (!isBufferLike(value)) newBuffers[key] = value;
15677
+ }
15678
+ } else {
15679
+ newBuffers = {};
15680
+ }
15681
+ return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15682
+ });
15683
+ },
15684
+ [store]
15685
+ );
15686
+ const disposeBuffers = useCallback(
15687
+ (names, targetScope) => {
15688
+ const nameArray = Array.isArray(names) ? names : [names];
15689
+ const state = store.getState();
15690
+ for (const name of nameArray) {
15691
+ const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
15692
+ if (buffer && isBufferLike(buffer)) {
15693
+ disposeBuffer(buffer);
15694
+ }
15695
+ }
15696
+ removeBuffers(names, targetScope);
15697
+ },
15698
+ [store, removeBuffers]
15699
+ );
15700
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15701
+ const storeBuffers = usePrimaryThree((s) => s.buffers);
15702
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15703
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15704
+ const readerDep = isReader ? storeBuffers : null;
15705
+ const creatorDep = isReader ? null : hmrVersion;
15706
+ const buffers = useMemo(() => {
15707
+ if (creatorOrScope === void 0) {
15708
+ return storeBuffers;
15709
+ }
15710
+ if (typeof creatorOrScope === "string") {
15711
+ const scopeData = storeBuffers[creatorOrScope];
15712
+ if (scopeData && !isBufferLike(scopeData)) return scopeData;
15713
+ return {};
15714
+ }
15715
+ const state = store.getState();
15716
+ const set = store.setState;
15717
+ const creator = creatorOrScope;
15718
+ const wrappedState = createLazyCreatorState(state);
15719
+ const created = creator(wrappedState);
15720
+ const result = {};
15721
+ let hasNewBuffers = false;
15722
+ if (scope) {
15723
+ const currentScope = state.buffers[scope] ?? {};
15724
+ for (const [name, buffer] of Object.entries(created)) {
15725
+ if (currentScope[name]) {
15726
+ result[name] = currentScope[name];
15727
+ } else {
15728
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15729
+ buffer.setName(`${scope}.${name}`);
15730
+ }
15731
+ result[name] = buffer;
15732
+ hasNewBuffers = true;
15733
+ }
15734
+ }
15735
+ if (hasNewBuffers) {
15736
+ set((s) => ({
15737
+ buffers: {
15738
+ ...s.buffers,
15739
+ [scope]: { ...s.buffers[scope], ...result }
15740
+ }
15741
+ }));
15742
+ }
15743
+ return result;
15744
+ }
15745
+ for (const [name, buffer] of Object.entries(created)) {
15746
+ const existing = state.buffers[name];
15747
+ if (existing && isBufferLike(existing)) {
15748
+ result[name] = existing;
15749
+ } else {
15750
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15751
+ buffer.setName(name);
15752
+ }
15753
+ result[name] = buffer;
15754
+ hasNewBuffers = true;
15755
+ }
15756
+ }
15757
+ if (hasNewBuffers) {
15758
+ set((s) => ({ buffers: { ...s.buffers, ...result } }));
15759
+ }
15760
+ return result;
15761
+ }, [store, scopeDep, readerDep, creatorDep]);
15762
+ return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
15763
+ }
15764
+ function rebuildAllBuffers(store, scope) {
15765
+ store = store.getState().primaryStore ?? store;
15766
+ store.setState((state) => {
15767
+ let newBuffers = state.buffers;
15768
+ if (scope && scope !== "root") {
15769
+ const { [scope]: _, ...rest } = state.buffers;
15770
+ newBuffers = rest;
15771
+ } else if (scope === "root") {
15772
+ newBuffers = {};
15773
+ for (const [key, value] of Object.entries(state.buffers)) {
15774
+ if (!isBufferLike(value)) newBuffers[key] = value;
15775
+ }
15776
+ } else {
15777
+ newBuffers = {};
15778
+ }
15779
+ return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15780
+ });
15781
+ }
15782
+
15783
+ const isStorageLike = (value) => {
15784
+ if (value === null || typeof value !== "object") return false;
15785
+ if ("isTexture" in value) return true;
15786
+ if ("isData3DTexture" in value) return true;
15787
+ if ("uuid" in value || "nodeType" in value) return true;
15788
+ return false;
15789
+ };
15790
+ const disposeStorage = (storage) => {
15791
+ if (storage === null || typeof storage !== "object") return;
15792
+ if ("dispose" in storage && typeof storage.dispose === "function") {
15793
+ storage.dispose();
15794
+ }
15795
+ };
15796
+ function useGPUStorage(creatorOrScope, scope) {
15797
+ const store = usePrimaryStore();
15798
+ const removeStorage = useCallback(
15799
+ (names, targetScope) => {
15800
+ const nameArray = Array.isArray(names) ? names : [names];
15801
+ store.setState((state) => {
15802
+ if (targetScope) {
15803
+ const currentScope = { ...state.gpuStorage[targetScope] };
15804
+ for (const name of nameArray) delete currentScope[name];
15805
+ return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
15806
+ }
15807
+ const gpuStorage2 = { ...state.gpuStorage };
15808
+ for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
15809
+ return { gpuStorage: gpuStorage2 };
15810
+ });
15811
+ },
15812
+ [store]
15813
+ );
15814
+ const clearStorage = useCallback(
15815
+ (targetScope) => {
15816
+ store.setState((state) => {
15817
+ if (targetScope && targetScope !== "root") {
15818
+ const { [targetScope]: _, ...rest } = state.gpuStorage;
15819
+ return { gpuStorage: rest };
15820
+ }
15821
+ if (targetScope === "root") {
15822
+ const gpuStorage2 = {};
15823
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15824
+ if (!isStorageLike(value)) gpuStorage2[key] = value;
15825
+ }
15826
+ return { gpuStorage: gpuStorage2 };
15827
+ }
15828
+ return { gpuStorage: {} };
15829
+ });
15830
+ },
15831
+ [store]
15832
+ );
15833
+ const rebuildStorage = useCallback(
15834
+ (targetScope) => {
15835
+ store.setState((state) => {
15836
+ let newStorage = state.gpuStorage;
15837
+ if (targetScope && targetScope !== "root") {
15838
+ const { [targetScope]: _, ...rest } = state.gpuStorage;
15839
+ newStorage = rest;
15840
+ } else if (targetScope === "root") {
15841
+ newStorage = {};
15842
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15843
+ if (!isStorageLike(value)) newStorage[key] = value;
15844
+ }
15845
+ } else {
15846
+ newStorage = {};
15847
+ }
15848
+ return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15849
+ });
15850
+ },
15851
+ [store]
15852
+ );
15853
+ const disposeStorageFn = useCallback(
15854
+ (names, targetScope) => {
15855
+ const nameArray = Array.isArray(names) ? names : [names];
15856
+ const state = store.getState();
15857
+ for (const name of nameArray) {
15858
+ const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
15859
+ if (storage && isStorageLike(storage)) {
15860
+ disposeStorage(storage);
15861
+ }
15862
+ }
15863
+ removeStorage(names, targetScope);
15864
+ },
15865
+ [store, removeStorage]
15866
+ );
15867
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15868
+ const storeStorage = usePrimaryThree((s) => s.gpuStorage);
15869
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15870
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15871
+ const readerDep = isReader ? storeStorage : null;
15872
+ const creatorDep = isReader ? null : hmrVersion;
15873
+ const gpuStorage = useMemo(() => {
15874
+ if (creatorOrScope === void 0) {
15875
+ return storeStorage;
15876
+ }
15877
+ if (typeof creatorOrScope === "string") {
15878
+ const scopeData = storeStorage[creatorOrScope];
15879
+ if (scopeData && !isStorageLike(scopeData)) return scopeData;
15880
+ return {};
15881
+ }
15520
15882
  const state = store.getState();
15521
- return creator(state);
15522
- }, [store, creator, uniforms, nodes, textures]);
15883
+ const set = store.setState;
15884
+ const creator = creatorOrScope;
15885
+ const wrappedState = createLazyCreatorState(state);
15886
+ const created = creator(wrappedState);
15887
+ const result = {};
15888
+ let hasNewStorage = false;
15889
+ if (scope) {
15890
+ const currentScope = state.gpuStorage[scope] ?? {};
15891
+ for (const [name, storage] of Object.entries(created)) {
15892
+ if (currentScope[name]) {
15893
+ result[name] = currentScope[name];
15894
+ } else {
15895
+ if ("setName" in storage && typeof storage.setName === "function") {
15896
+ storage.setName(`${scope}.${name}`);
15897
+ }
15898
+ if ("name" in storage && typeof storage.name === "string") {
15899
+ storage.name = `${scope}.${name}`;
15900
+ }
15901
+ result[name] = storage;
15902
+ hasNewStorage = true;
15903
+ }
15904
+ }
15905
+ if (hasNewStorage) {
15906
+ set((s) => ({
15907
+ gpuStorage: {
15908
+ ...s.gpuStorage,
15909
+ [scope]: { ...s.gpuStorage[scope], ...result }
15910
+ }
15911
+ }));
15912
+ }
15913
+ return result;
15914
+ }
15915
+ for (const [name, storage] of Object.entries(created)) {
15916
+ const existing = state.gpuStorage[name];
15917
+ if (existing && isStorageLike(existing)) {
15918
+ result[name] = existing;
15919
+ } else {
15920
+ if ("setName" in storage && typeof storage.setName === "function") {
15921
+ storage.setName(name);
15922
+ }
15923
+ if ("name" in storage && typeof storage.name === "string") {
15924
+ storage.name = name;
15925
+ }
15926
+ result[name] = storage;
15927
+ hasNewStorage = true;
15928
+ }
15929
+ }
15930
+ if (hasNewStorage) {
15931
+ set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
15932
+ }
15933
+ return result;
15934
+ }, [store, scopeDep, readerDep, creatorDep]);
15935
+ return {
15936
+ ...gpuStorage,
15937
+ removeStorage,
15938
+ clearStorage,
15939
+ rebuildStorage,
15940
+ disposeStorage: disposeStorageFn
15941
+ };
15942
+ }
15943
+ function rebuildAllStorage(store, scope) {
15944
+ store = store.getState().primaryStore ?? store;
15945
+ store.setState((state) => {
15946
+ let newStorage = state.gpuStorage;
15947
+ if (scope && scope !== "root") {
15948
+ const { [scope]: _, ...rest } = state.gpuStorage;
15949
+ newStorage = rest;
15950
+ } else if (scope === "root") {
15951
+ newStorage = {};
15952
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15953
+ if (!isStorageLike(value)) newStorage[key] = value;
15954
+ }
15955
+ } else {
15956
+ newStorage = {};
15957
+ }
15958
+ return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15959
+ });
15523
15960
  }
15524
15961
 
15525
- function usePostProcessing(mainCB, setupCB) {
15962
+ function useRenderPipeline(mainCB, setupCB) {
15526
15963
  const store = useStore();
15527
15964
  const { scene, camera, renderer, isLegacy } = useThree();
15528
15965
  const callbacksRanRef = useRef(false);
@@ -15532,12 +15969,16 @@ function usePostProcessing(mainCB, setupCB) {
15532
15969
  mainCBRef.current = mainCB;
15533
15970
  setupCBRef.current = setupCB;
15534
15971
  const [rebuildVersion, setRebuildVersion] = useState(0);
15972
+ useEffect(() => {
15973
+ callbacksRanRef.current = false;
15974
+ scenePassCacheRef.current = null;
15975
+ }, []);
15535
15976
  const clearPasses = useCallback(() => {
15536
15977
  store.setState({ passes: {} });
15537
15978
  }, [store]);
15538
15979
  const reset = useCallback(() => {
15539
15980
  store.setState({
15540
- postProcessing: null,
15981
+ renderPipeline: null,
15541
15982
  passes: {}
15542
15983
  });
15543
15984
  callbacksRanRef.current = false;
@@ -15550,13 +15991,13 @@ function usePostProcessing(mainCB, setupCB) {
15550
15991
  }, []);
15551
15992
  useLayoutEffect(() => {
15552
15993
  if (isLegacy) {
15553
- throw new Error("usePostProcessing is only available with WebGPU renderer. Set renderer prop on Canvas.");
15994
+ throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
15554
15995
  }
15555
15996
  if (!renderer || !scene || !camera) return;
15556
15997
  const state = store.getState();
15557
15998
  const set = store.setState;
15558
15999
  try {
15559
- let pp = state.postProcessing;
16000
+ let pp = state.renderPipeline;
15560
16001
  let currentPasses = { ...state.passes };
15561
16002
  let justCreatedPP = false;
15562
16003
  if (!pp) {
@@ -15573,7 +16014,7 @@ function usePostProcessing(mainCB, setupCB) {
15573
16014
  }
15574
16015
  currentPasses.scenePass = scenePass;
15575
16016
  if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
15576
- set({ postProcessing: pp, passes: currentPasses });
16017
+ set({ renderPipeline: pp, passes: currentPasses });
15577
16018
  const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
15578
16019
  if (shouldRunCallbacks) {
15579
16020
  if (setupCBRef.current) {
@@ -15595,22 +16036,22 @@ function usePostProcessing(mainCB, setupCB) {
15595
16036
  callbacksRanRef.current = true;
15596
16037
  }
15597
16038
  } catch (error) {
15598
- console.error("[usePostProcessing] Setup error:", error);
16039
+ console.error("[useRenderPipeline] Setup error:", error);
15599
16040
  }
15600
16041
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
15601
16042
  const passes = useThree((s) => s.passes);
15602
- const postProcessing = useThree((s) => s.postProcessing);
16043
+ const renderPipeline = useThree((s) => s.renderPipeline);
15603
16044
  return {
15604
16045
  passes,
15605
- postProcessing,
16046
+ renderPipeline,
15606
16047
  clearPasses,
15607
16048
  reset,
15608
16049
  rebuild,
15609
- // isReady indicates if PostProcessing is configured and ready for rendering
15610
- isReady: postProcessing !== null
16050
+ // isReady indicates if RenderPipeline is configured and ready for rendering
16051
+ isReady: renderPipeline !== null
15611
16052
  };
15612
16053
  }
15613
16054
 
15614
16055
  extend(THREE);
15615
16056
 
15616
- export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };
16057
+ export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useBuffers, useEnvironment, useFrame, useGPUStorage, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, useRenderPipeline, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };