@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.
@@ -6,13 +6,21 @@ const jsxRuntime = require('react/jsx-runtime');
6
6
  const React = require('react');
7
7
  const useMeasure = require('react-use-measure');
8
8
  const itsFine = require('its-fine');
9
+ const fiber = require('@react-three/fiber');
10
+ const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
11
+ const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
12
+ const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
13
+ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
14
+ const gainmapJs = require('@monogrid/gainmap-js');
9
15
  const Tb = require('scheduler');
10
16
  const traditional = require('zustand/traditional');
17
+ const scheduler = require('@pmndrs/scheduler');
11
18
  const suspendReact = require('suspend-react');
12
19
  const lite = require('dequal/lite');
13
20
  require('zustand/shallow');
14
21
  const tsl = require('three/tsl');
15
22
 
23
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
16
24
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
17
25
 
18
26
  function _interopNamespaceCompat(e) {
@@ -66,9 +74,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
66
74
  WebGLRenderer: WebGLRenderer
67
75
  }, [webgpu__namespace]);
68
76
 
69
- var __defProp$2 = Object.defineProperty;
70
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
71
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
77
+ const primaryRegistry = /* @__PURE__ */ new Map();
78
+ const pendingSubscribers = /* @__PURE__ */ new Map();
79
+ function registerPrimary(id, renderer, store) {
80
+ if (primaryRegistry.has(id)) {
81
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
82
+ }
83
+ const entry = { renderer, store };
84
+ primaryRegistry.set(id, entry);
85
+ const subscribers = pendingSubscribers.get(id);
86
+ if (subscribers) {
87
+ subscribers.forEach((callback) => callback(entry));
88
+ pendingSubscribers.delete(id);
89
+ }
90
+ return () => {
91
+ const currentEntry = primaryRegistry.get(id);
92
+ if (currentEntry?.renderer === renderer) {
93
+ primaryRegistry.delete(id);
94
+ }
95
+ };
96
+ }
97
+ function getPrimary(id) {
98
+ return primaryRegistry.get(id);
99
+ }
100
+ function waitForPrimary(id, timeout = 5e3) {
101
+ const existing = primaryRegistry.get(id);
102
+ if (existing) {
103
+ return Promise.resolve(existing);
104
+ }
105
+ return new Promise((resolve, reject) => {
106
+ const timeoutId = setTimeout(() => {
107
+ const subscribers = pendingSubscribers.get(id);
108
+ if (subscribers) {
109
+ const index = subscribers.indexOf(callback);
110
+ if (index !== -1) subscribers.splice(index, 1);
111
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
112
+ }
113
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
114
+ }, timeout);
115
+ const callback = (entry) => {
116
+ clearTimeout(timeoutId);
117
+ resolve(entry);
118
+ };
119
+ if (!pendingSubscribers.has(id)) {
120
+ pendingSubscribers.set(id, []);
121
+ }
122
+ pendingSubscribers.get(id).push(callback);
123
+ });
124
+ }
125
+ function hasPrimary(id) {
126
+ return primaryRegistry.has(id);
127
+ }
128
+ function unregisterPrimary(id) {
129
+ primaryRegistry.delete(id);
130
+ }
131
+ function getPrimaryIds() {
132
+ return Array.from(primaryRegistry.keys());
133
+ }
134
+
135
+ const presetsObj = {
136
+ apartment: "lebombo_1k.hdr",
137
+ city: "potsdamer_platz_1k.hdr",
138
+ dawn: "kiara_1_dawn_1k.hdr",
139
+ forest: "forest_slope_1k.hdr",
140
+ lobby: "st_fagans_interior_1k.hdr",
141
+ night: "dikhololo_night_1k.hdr",
142
+ park: "rooitou_park_1k.hdr",
143
+ studio: "studio_small_03_1k.hdr",
144
+ sunset: "venice_sunset_1k.hdr",
145
+ warehouse: "empty_warehouse_01_1k.hdr"
146
+ };
147
+
148
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
149
+ const isArray = (arr) => Array.isArray(arr);
150
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
151
+ function useEnvironment({
152
+ files = defaultFiles,
153
+ path = "",
154
+ preset = void 0,
155
+ colorSpace = void 0,
156
+ extensions
157
+ } = {}) {
158
+ if (preset) {
159
+ validatePreset(preset);
160
+ files = presetsObj[preset];
161
+ path = CUBEMAP_ROOT;
162
+ }
163
+ const multiFile = isArray(files);
164
+ const { extension, isCubemap } = getExtension(files);
165
+ const loader = getLoader$1(extension);
166
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
167
+ const renderer = fiber.useThree((state) => state.renderer);
168
+ React.useLayoutEffect(() => {
169
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
170
+ function clearGainmapTexture() {
171
+ fiber.useLoader.clear(loader, multiFile ? [files] : files);
172
+ }
173
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
174
+ }, [extension, files, loader, multiFile, renderer.domElement]);
175
+ const loaderResult = fiber.useLoader(
176
+ loader,
177
+ multiFile ? [files] : files,
178
+ (loader2) => {
179
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
180
+ loader2.setRenderer?.(renderer);
181
+ }
182
+ loader2.setPath?.(path);
183
+ if (extensions) extensions(loader2);
184
+ }
185
+ );
186
+ let texture = multiFile ? (
187
+ // @ts-ignore
188
+ loaderResult[0]
189
+ ) : loaderResult;
190
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
191
+ texture = texture.renderTarget?.texture;
192
+ }
193
+ texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
194
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
195
+ return texture;
196
+ }
197
+ const preloadDefaultOptions = {
198
+ files: defaultFiles,
199
+ path: "",
200
+ preset: void 0,
201
+ extensions: void 0
202
+ };
203
+ useEnvironment.preload = (preloadOptions) => {
204
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
205
+ let { files, path = "" } = options;
206
+ const { preset, extensions } = options;
207
+ if (preset) {
208
+ validatePreset(preset);
209
+ files = presetsObj[preset];
210
+ path = CUBEMAP_ROOT;
211
+ }
212
+ const { extension } = getExtension(files);
213
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
214
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
215
+ }
216
+ const loader = getLoader$1(extension);
217
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
218
+ fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
219
+ loader2.setPath?.(path);
220
+ if (extensions) extensions(loader2);
221
+ });
222
+ };
223
+ const clearDefaultOptins = {
224
+ files: defaultFiles,
225
+ preset: void 0
226
+ };
227
+ useEnvironment.clear = (clearOptions) => {
228
+ const options = { ...clearDefaultOptins, ...clearOptions };
229
+ let { files } = options;
230
+ const { preset } = options;
231
+ if (preset) {
232
+ validatePreset(preset);
233
+ files = presetsObj[preset];
234
+ }
235
+ const { extension } = getExtension(files);
236
+ const loader = getLoader$1(extension);
237
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
238
+ fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
239
+ };
240
+ function validatePreset(preset) {
241
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
242
+ }
243
+ function getExtension(files) {
244
+ const isCubemap = isArray(files) && files.length === 6;
245
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
246
+ const firstEntry = isArray(files) ? files[0] : files;
247
+ const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
248
+ return { extension, isCubemap, isGainmap };
249
+ }
250
+ function getLoader$1(extension) {
251
+ const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
252
+ return loader;
253
+ }
254
+
255
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
256
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
257
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
258
+ sceneProps = {
259
+ backgroundBlurriness: 0,
260
+ backgroundIntensity: 1,
261
+ backgroundRotation: [0, 0, 0],
262
+ environmentIntensity: 1,
263
+ environmentRotation: [0, 0, 0],
264
+ ...sceneProps
265
+ };
266
+ const target = resolveScene(scene || defaultScene);
267
+ const oldbg = target.background;
268
+ const oldenv = target.environment;
269
+ const oldSceneProps = {
270
+ // @ts-ignore
271
+ backgroundBlurriness: target.backgroundBlurriness,
272
+ // @ts-ignore
273
+ backgroundIntensity: target.backgroundIntensity,
274
+ // @ts-ignore
275
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
276
+ // @ts-ignore
277
+ environmentIntensity: target.environmentIntensity,
278
+ // @ts-ignore
279
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
280
+ };
281
+ if (background !== "only") target.environment = texture;
282
+ if (background) target.background = texture;
283
+ fiber.applyProps(target, sceneProps);
284
+ return () => {
285
+ if (background !== "only") target.environment = oldenv;
286
+ if (background) target.background = oldbg;
287
+ fiber.applyProps(target, oldSceneProps);
288
+ };
289
+ }
290
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
291
+ const defaultScene = fiber.useThree((state) => state.scene);
292
+ React__namespace.useLayoutEffect(() => {
293
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
294
+ });
295
+ return null;
296
+ }
297
+ function EnvironmentCube({
298
+ background = false,
299
+ scene,
300
+ blur,
301
+ backgroundBlurriness,
302
+ backgroundIntensity,
303
+ backgroundRotation,
304
+ environmentIntensity,
305
+ environmentRotation,
306
+ ...rest
307
+ }) {
308
+ const texture = useEnvironment(rest);
309
+ const defaultScene = fiber.useThree((state) => state.scene);
310
+ React__namespace.useLayoutEffect(() => {
311
+ return setEnvProps(background, scene, defaultScene, texture, {
312
+ backgroundBlurriness: blur ?? backgroundBlurriness,
313
+ backgroundIntensity,
314
+ backgroundRotation,
315
+ environmentIntensity,
316
+ environmentRotation
317
+ });
318
+ });
319
+ React__namespace.useEffect(() => {
320
+ return () => {
321
+ texture.dispose();
322
+ };
323
+ }, [texture]);
324
+ return null;
325
+ }
326
+ function EnvironmentPortal({
327
+ children,
328
+ near = 0.1,
329
+ far = 1e3,
330
+ resolution = 256,
331
+ frames = 1,
332
+ map,
333
+ background = false,
334
+ blur,
335
+ backgroundBlurriness,
336
+ backgroundIntensity,
337
+ backgroundRotation,
338
+ environmentIntensity,
339
+ environmentRotation,
340
+ scene,
341
+ files,
342
+ path,
343
+ preset = void 0,
344
+ extensions
345
+ }) {
346
+ const gl = fiber.useThree((state) => state.gl);
347
+ const defaultScene = fiber.useThree((state) => state.scene);
348
+ const camera = React__namespace.useRef(null);
349
+ const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
350
+ const fbo = React__namespace.useMemo(() => {
351
+ const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
352
+ fbo2.texture.type = webgpu.HalfFloatType;
353
+ return fbo2;
354
+ }, [resolution]);
355
+ React__namespace.useEffect(() => {
356
+ return () => {
357
+ fbo.dispose();
358
+ };
359
+ }, [fbo]);
360
+ React__namespace.useLayoutEffect(() => {
361
+ if (frames === 1) {
362
+ const autoClear = gl.autoClear;
363
+ gl.autoClear = true;
364
+ camera.current.update(gl, virtualScene);
365
+ gl.autoClear = autoClear;
366
+ }
367
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
368
+ backgroundBlurriness: blur ?? backgroundBlurriness,
369
+ backgroundIntensity,
370
+ backgroundRotation,
371
+ environmentIntensity,
372
+ environmentRotation
373
+ });
374
+ }, [
375
+ children,
376
+ virtualScene,
377
+ fbo.texture,
378
+ scene,
379
+ defaultScene,
380
+ background,
381
+ frames,
382
+ gl,
383
+ blur,
384
+ backgroundBlurriness,
385
+ backgroundIntensity,
386
+ backgroundRotation,
387
+ environmentIntensity,
388
+ environmentRotation
389
+ ]);
390
+ let count = 1;
391
+ fiber.useFrame(() => {
392
+ if (frames === Infinity || count < frames) {
393
+ const autoClear = gl.autoClear;
394
+ gl.autoClear = true;
395
+ camera.current.update(gl, virtualScene);
396
+ gl.autoClear = autoClear;
397
+ count++;
398
+ }
399
+ });
400
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
401
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
402
+ children,
403
+ /* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
404
+ files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
405
+ ] }),
406
+ virtualScene
407
+ ) });
408
+ }
409
+ function EnvironmentGround(props) {
410
+ const textureDefault = useEnvironment(props);
411
+ const texture = props.map || textureDefault;
412
+ React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
413
+ React__namespace.useEffect(() => {
414
+ return () => {
415
+ textureDefault.dispose();
416
+ };
417
+ }, [textureDefault]);
418
+ const height = props.ground?.height ?? 15;
419
+ const radius = props.ground?.radius ?? 60;
420
+ const scale = props.ground?.scale ?? 1e3;
421
+ const args = React__namespace.useMemo(
422
+ () => [texture, height, radius],
423
+ [texture, height, radius]
424
+ );
425
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
426
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
427
+ /* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
428
+ ] });
429
+ }
430
+ function EnvironmentColor({ color, scene }) {
431
+ const defaultScene = fiber.useThree((state) => state.scene);
432
+ React__namespace.useLayoutEffect(() => {
433
+ if (color === void 0) return;
434
+ const target = resolveScene(scene || defaultScene);
435
+ const oldBg = target.background;
436
+ target.background = new webgpu.Color(color);
437
+ return () => {
438
+ target.background = oldBg;
439
+ };
440
+ });
441
+ return null;
442
+ }
443
+ function EnvironmentDualSource(props) {
444
+ const { backgroundFiles, ...envProps } = props;
445
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
446
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
447
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
448
+ ] });
449
+ }
450
+ function Environment(props) {
451
+ if (props.color && !props.files && !props.preset && !props.map) {
452
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
453
+ }
454
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
455
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
456
+ }
457
+ return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
458
+ }
459
+
460
+ var __defProp$1 = Object.defineProperty;
461
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
462
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
72
463
  const act = React__namespace["act"];
73
464
  const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
74
465
  function useMutableCallback(fn) {
@@ -100,7 +491,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
100
491
  return _a = class extends React__namespace.Component {
101
492
  constructor() {
102
493
  super(...arguments);
103
- __publicField$2(this, "state", { error: false });
494
+ __publicField$1(this, "state", { error: false });
104
495
  }
105
496
  componentDidCatch(err) {
106
497
  this.props.set(err);
@@ -108,7 +499,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
108
499
  render() {
109
500
  return this.state.error ? null : this.props.children;
110
501
  }
111
- }, __publicField$2(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
502
+ }, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
112
503
  })();
113
504
 
114
505
  const is = {
@@ -245,7 +636,8 @@ function prepare(target, root, type, props) {
245
636
  object,
246
637
  eventCount: 0,
247
638
  handlers: {},
248
- isHidden: false
639
+ isHidden: false,
640
+ deferredRefs: []
249
641
  };
250
642
  if (object) object.__r3f = instance;
251
643
  }
@@ -294,7 +686,7 @@ function createOcclusionObserverNode(store, uniform) {
294
686
  let occlusionSetupPromise = null;
295
687
  function enableOcclusion(store) {
296
688
  const state = store.getState();
297
- const { internal, renderer, rootScene } = state;
689
+ const { internal, renderer } = state;
298
690
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
299
691
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
300
692
  if (!hasOcclusionSupport) {
@@ -457,6 +849,22 @@ function hasVisibilityHandlers(handlers) {
457
849
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
458
850
  }
459
851
 
852
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
853
+ function fromRef(ref) {
854
+ return { [FROM_REF]: ref };
855
+ }
856
+ function isFromRef(value) {
857
+ return value !== null && typeof value === "object" && FROM_REF in value;
858
+ }
859
+
860
+ const ONCE = Symbol.for("@react-three/fiber.once");
861
+ function once(...args) {
862
+ return { [ONCE]: args.length ? args : true };
863
+ }
864
+ function isOnce(value) {
865
+ return value !== null && typeof value === "object" && ONCE in value;
866
+ }
867
+
460
868
  const RESERVED_PROPS = [
461
869
  "children",
462
870
  "key",
@@ -527,7 +935,7 @@ function getMemoizedPrototype(root) {
527
935
  ctor = new root.constructor();
528
936
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
529
937
  }
530
- } catch (e) {
938
+ } catch {
531
939
  }
532
940
  return ctor;
533
941
  }
@@ -558,7 +966,7 @@ function applyProps(object, props) {
558
966
  const rootState = instance && findInitialRoot(instance).getState();
559
967
  const prevHandlers = instance?.eventCount;
560
968
  for (const prop in props) {
561
- let value = props[prop];
969
+ const value = props[prop];
562
970
  if (RESERVED_PROPS.includes(prop)) continue;
563
971
  if (instance && EVENT_REGEX.test(prop)) {
564
972
  if (typeof value === "function") instance.handlers[prop] = value;
@@ -573,6 +981,25 @@ function applyProps(object, props) {
573
981
  continue;
574
982
  }
575
983
  if (value === void 0) continue;
984
+ if (isFromRef(value)) {
985
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
986
+ continue;
987
+ }
988
+ if (isOnce(value)) {
989
+ if (instance?.appliedOnce?.has(prop)) continue;
990
+ if (instance) {
991
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
992
+ instance.appliedOnce.add(prop);
993
+ }
994
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
995
+ const args = value[ONCE];
996
+ if (typeof targetRoot[targetKey] === "function") {
997
+ targetRoot[targetKey](...args === true ? [] : args);
998
+ } else if (args !== true && args.length > 0) {
999
+ targetRoot[targetKey] = args[0];
1000
+ }
1001
+ continue;
1002
+ }
576
1003
  let { root, key, target } = resolve(object, prop);
577
1004
  if (target === void 0 && (typeof root !== "object" || root === null)) {
578
1005
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -595,7 +1022,10 @@ function applyProps(object, props) {
595
1022
  else target.set(value);
596
1023
  } else {
597
1024
  root[key] = value;
598
- if (rootState && !rootState.linear && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
1025
+ if (key.endsWith("Node") && root.isMaterial) {
1026
+ root.needsUpdate = true;
1027
+ }
1028
+ if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
599
1029
  root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
600
1030
  root[key].colorSpace = rootState.textureColorSpace;
601
1031
  }
@@ -628,38 +1058,60 @@ function applyProps(object, props) {
628
1058
  return object;
629
1059
  }
630
1060
 
1061
+ const DEFAULT_POINTER_ID = 0;
1062
+ const XR_POINTER_ID_START = 1e3;
1063
+ function getPointerState(internal, pointerId) {
1064
+ let state = internal.pointerMap.get(pointerId);
1065
+ if (!state) {
1066
+ state = {
1067
+ hovered: /* @__PURE__ */ new Map(),
1068
+ captured: /* @__PURE__ */ new Map(),
1069
+ initialClick: [0, 0],
1070
+ initialHits: []
1071
+ };
1072
+ internal.pointerMap.set(pointerId, state);
1073
+ }
1074
+ return state;
1075
+ }
1076
+ function getPointerId(event) {
1077
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1078
+ }
631
1079
  function makeId(event) {
632
1080
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
633
1081
  }
634
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
635
- const captureData = captures.get(obj);
1082
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1083
+ const pointerState = internal.pointerMap.get(pointerId);
1084
+ if (!pointerState) return;
1085
+ const captureData = pointerState.captured.get(obj);
636
1086
  if (captureData) {
637
- captures.delete(obj);
638
- if (captures.size === 0) {
639
- capturedMap.delete(pointerId);
640
- captureData.target.releasePointerCapture(pointerId);
641
- }
1087
+ pointerState.captured.delete(obj);
1088
+ captureData.target.releasePointerCapture(pointerId);
642
1089
  }
643
1090
  }
644
1091
  function removeInteractivity(store, object) {
645
1092
  const { internal } = store.getState();
646
1093
  internal.interaction = internal.interaction.filter((o) => o !== object);
647
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
648
- internal.hovered.forEach((value, key) => {
649
- if (value.eventObject === object || value.object === object) {
650
- internal.hovered.delete(key);
1094
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1095
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1096
+ pointerState.hovered.forEach((value, key) => {
1097
+ if (value.eventObject === object || value.object === object) {
1098
+ pointerState.hovered.delete(key);
1099
+ }
1100
+ });
1101
+ if (pointerState.captured.has(object)) {
1102
+ releaseInternalPointerCapture(internal, object, pointerId);
651
1103
  }
652
- });
653
- internal.capturedMap.forEach((captures, pointerId) => {
654
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
655
- });
1104
+ }
656
1105
  unregisterVisibility(store, object);
657
1106
  }
658
1107
  function createEvents(store) {
659
- function calculateDistance(event) {
1108
+ function calculateDistance(event, pointerId) {
660
1109
  const { internal } = store.getState();
661
- const dx = event.offsetX - internal.initialClick[0];
662
- const dy = event.offsetY - internal.initialClick[1];
1110
+ const pointerState = internal.pointerMap.get(pointerId);
1111
+ if (!pointerState) return 0;
1112
+ const [initialX, initialY] = pointerState.initialClick;
1113
+ const dx = event.offsetX - initialX;
1114
+ const dy = event.offsetY - initialY;
663
1115
  return Math.round(Math.sqrt(dx * dx + dy * dy));
664
1116
  }
665
1117
  function filterPointerEvents(objects) {
@@ -695,6 +1147,15 @@ function createEvents(store) {
695
1147
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
696
1148
  }
697
1149
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1150
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1151
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1152
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1153
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1154
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1155
+ if (aInteractivePriority !== bInteractivePriority) {
1156
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1157
+ }
1158
+ }
698
1159
  const aState = getRootState(a.object);
699
1160
  const bState = getRootState(b.object);
700
1161
  const aPriority = aState?.events?.priority ?? 1;
@@ -710,14 +1171,19 @@ function createEvents(store) {
710
1171
  for (const hit of hits) {
711
1172
  let eventObject = hit.object;
712
1173
  while (eventObject) {
713
- if (eventObject.__r3f?.eventCount)
1174
+ if (eventObject.__r3f?.eventCount) {
714
1175
  intersections.push({ ...hit, eventObject });
1176
+ }
715
1177
  eventObject = eventObject.parent;
716
1178
  }
717
1179
  }
718
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
719
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
720
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1180
+ if ("pointerId" in event) {
1181
+ const pointerId = event.pointerId;
1182
+ const pointerState = state.internal.pointerMap.get(pointerId);
1183
+ if (pointerState?.captured.size) {
1184
+ for (const captureData of pointerState.captured.values()) {
1185
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1186
+ }
721
1187
  }
722
1188
  }
723
1189
  return intersections;
@@ -730,28 +1196,26 @@ function createEvents(store) {
730
1196
  if (state) {
731
1197
  const { raycaster, pointer, camera, internal } = state;
732
1198
  const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
733
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1199
+ const hasPointerCapture = (id) => {
1200
+ const pointerState = internal.pointerMap.get(id);
1201
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1202
+ };
734
1203
  const setPointerCapture = (id) => {
735
1204
  const captureData = { intersection: hit, target: event.target };
736
- if (internal.capturedMap.has(id)) {
737
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
738
- } else {
739
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
740
- }
1205
+ const pointerState = getPointerState(internal, id);
1206
+ pointerState.captured.set(hit.eventObject, captureData);
741
1207
  event.target.setPointerCapture(id);
742
1208
  };
743
1209
  const releasePointerCapture = (id) => {
744
- const captures = internal.capturedMap.get(id);
745
- if (captures) {
746
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
747
- }
1210
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
748
1211
  };
749
- let extractEventProps = {};
750
- for (let prop in event) {
751
- let property = event[prop];
1212
+ const extractEventProps = {};
1213
+ for (const prop in event) {
1214
+ const property = event[prop];
752
1215
  if (typeof property !== "function") extractEventProps[prop] = property;
753
1216
  }
754
- let raycastEvent = {
1217
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1218
+ const raycastEvent = {
755
1219
  ...hit,
756
1220
  ...extractEventProps,
757
1221
  pointer,
@@ -761,18 +1225,19 @@ function createEvents(store) {
761
1225
  unprojectedPoint,
762
1226
  ray: raycaster.ray,
763
1227
  camera,
1228
+ pointerId: eventPointerId,
764
1229
  // Hijack stopPropagation, which just sets a flag
765
1230
  stopPropagation() {
766
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1231
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
767
1232
  if (
768
1233
  // ...if this pointer hasn't been captured
769
- !capturesForPointer || // ... or if the hit object is capturing the pointer
770
- capturesForPointer.has(hit.eventObject)
1234
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1235
+ pointerState.captured.has(hit.eventObject)
771
1236
  ) {
772
1237
  raycastEvent.stopped = localState.stopped = true;
773
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1238
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
774
1239
  const higher = intersections.slice(0, intersections.indexOf(hit));
775
- cancelPointer([...higher, hit]);
1240
+ cancelPointer([...higher, hit], eventPointerId);
776
1241
  }
777
1242
  }
778
1243
  },
@@ -788,15 +1253,18 @@ function createEvents(store) {
788
1253
  }
789
1254
  return intersections;
790
1255
  }
791
- function cancelPointer(intersections) {
1256
+ function cancelPointer(intersections, pointerId) {
792
1257
  const { internal } = store.getState();
793
- for (const hoveredObj of internal.hovered.values()) {
1258
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1259
+ const pointerState = internal.pointerMap.get(pid);
1260
+ if (!pointerState) return;
1261
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
794
1262
  if (!intersections.length || !intersections.find(
795
1263
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
796
1264
  )) {
797
1265
  const eventObject = hoveredObj.eventObject;
798
1266
  const instance = eventObject.__r3f;
799
- internal.hovered.delete(makeId(hoveredObj));
1267
+ pointerState.hovered.delete(hoveredId);
800
1268
  if (instance?.eventCount) {
801
1269
  const handlers = instance.handlers;
802
1270
  const data = { ...hoveredObj, intersections };
@@ -825,41 +1293,118 @@ function createEvents(store) {
825
1293
  instance?.handlers.onDropMissed?.(event);
826
1294
  }
827
1295
  }
1296
+ function cleanupPointer(pointerId) {
1297
+ const { internal } = store.getState();
1298
+ const pointerState = internal.pointerMap.get(pointerId);
1299
+ if (pointerState) {
1300
+ for (const [, hoveredObj] of pointerState.hovered) {
1301
+ const eventObject = hoveredObj.eventObject;
1302
+ const instance = eventObject.__r3f;
1303
+ if (instance?.eventCount) {
1304
+ const handlers = instance.handlers;
1305
+ const data = { ...hoveredObj, intersections: [] };
1306
+ handlers.onPointerOut?.(data);
1307
+ handlers.onPointerLeave?.(data);
1308
+ }
1309
+ }
1310
+ internal.pointerMap.delete(pointerId);
1311
+ }
1312
+ internal.pointerDirty.delete(pointerId);
1313
+ }
1314
+ function processDeferredPointer(event, pointerId) {
1315
+ const state = store.getState();
1316
+ const { internal } = state;
1317
+ if (!state.events.enabled) return;
1318
+ const filter = filterPointerEvents;
1319
+ const hits = intersect(event, filter);
1320
+ cancelPointer(hits, pointerId);
1321
+ function onIntersect(data) {
1322
+ const eventObject = data.eventObject;
1323
+ const instance = eventObject.__r3f;
1324
+ if (!instance?.eventCount) return;
1325
+ const handlers = instance.handlers;
1326
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1327
+ const id = makeId(data);
1328
+ const pointerState = getPointerState(internal, pointerId);
1329
+ const hoveredItem = pointerState.hovered.get(id);
1330
+ if (!hoveredItem) {
1331
+ pointerState.hovered.set(id, data);
1332
+ handlers.onPointerOver?.(data);
1333
+ handlers.onPointerEnter?.(data);
1334
+ } else if (hoveredItem.stopped) {
1335
+ data.stopPropagation();
1336
+ }
1337
+ }
1338
+ handlers.onPointerMove?.(data);
1339
+ }
1340
+ handleIntersects(hits, event, 0, onIntersect);
1341
+ }
828
1342
  function handlePointer(name) {
829
1343
  switch (name) {
830
1344
  case "onPointerLeave":
831
- case "onPointerCancel":
832
1345
  case "onDragLeave":
833
1346
  return () => cancelPointer([]);
1347
+ // Global cancel of these events
1348
+ case "onPointerCancel":
1349
+ return (event) => {
1350
+ const pointerId = getPointerId(event);
1351
+ cleanupPointer(pointerId);
1352
+ };
834
1353
  case "onLostPointerCapture":
835
1354
  return (event) => {
836
1355
  const { internal } = store.getState();
837
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1356
+ const pointerId = getPointerId(event);
1357
+ const pointerState = internal.pointerMap.get(pointerId);
1358
+ if (pointerState?.captured.size) {
838
1359
  requestAnimationFrame(() => {
839
- if (internal.capturedMap.has(event.pointerId)) {
840
- internal.capturedMap.delete(event.pointerId);
841
- cancelPointer([]);
1360
+ const pointerState2 = internal.pointerMap.get(pointerId);
1361
+ if (pointerState2?.captured.size) {
1362
+ pointerState2.captured.clear();
842
1363
  }
1364
+ cancelPointer([], pointerId);
843
1365
  });
844
1366
  }
845
1367
  };
846
1368
  }
847
1369
  return function handleEvent(event) {
848
1370
  const state = store.getState();
849
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1371
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1372
+ const pointerId = getPointerId(event);
850
1373
  internal.lastEvent.current = event;
851
- if (!state.events.enabled) return;
1374
+ if (!events.enabled) return;
852
1375
  const isPointerMove = name === "onPointerMove";
853
1376
  const isDragOver = name === "onDragOver";
854
1377
  const isDrop = name === "onDrop";
855
1378
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1379
+ const isPointerDown = name === "onPointerDown";
1380
+ const isPointerUp = name === "onPointerUp";
1381
+ const isWheel = name === "onWheel";
1382
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1383
+ if (isPointerMove && canDeferRaycasts) {
1384
+ events.compute?.(event, state);
1385
+ internal.pointerDirty.set(pointerId, event);
1386
+ return;
1387
+ }
1388
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1389
+ events.compute?.(event, state);
1390
+ internal.pointerDirty.set(pointerId, event);
1391
+ return;
1392
+ }
1393
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1394
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1395
+ internal.pointerDirty.delete(pointerId);
1396
+ processDeferredPointer(deferredEvent, pointerId);
1397
+ }
856
1398
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
857
1399
  const hits = intersect(event, filter);
858
- const delta = isClickEvent ? calculateDistance(event) : 0;
859
- if (name === "onPointerDown") {
860
- internal.initialClick = [event.offsetX, event.offsetY];
861
- internal.initialHits = hits.map((hit) => hit.eventObject);
862
- }
1400
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1401
+ if (isPointerDown) {
1402
+ const pointerState2 = getPointerState(internal, pointerId);
1403
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1404
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1405
+ }
1406
+ const pointerState = internal.pointerMap.get(pointerId);
1407
+ const initialHits = pointerState?.initialHits ?? [];
863
1408
  if (isClickEvent && !hits.length) {
864
1409
  if (delta <= 2) {
865
1410
  pointerMissed(event, internal.interaction);
@@ -874,7 +1419,9 @@ function createEvents(store) {
874
1419
  dropMissed(event, internal.interaction);
875
1420
  if (onDropMissed) onDropMissed(event);
876
1421
  }
877
- if (isPointerMove || isDragOver) cancelPointer(hits);
1422
+ if (isPointerMove || isDragOver) {
1423
+ cancelPointer(hits, pointerId);
1424
+ }
878
1425
  function onIntersect(data) {
879
1426
  const eventObject = data.eventObject;
880
1427
  const instance = eventObject.__r3f;
@@ -883,9 +1430,10 @@ function createEvents(store) {
883
1430
  if (isPointerMove) {
884
1431
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
885
1432
  const id = makeId(data);
886
- const hoveredItem = internal.hovered.get(id);
1433
+ const pointerState2 = getPointerState(internal, pointerId);
1434
+ const hoveredItem = pointerState2.hovered.get(id);
887
1435
  if (!hoveredItem) {
888
- internal.hovered.set(id, data);
1436
+ pointerState2.hovered.set(id, data);
889
1437
  handlers.onPointerOver?.(data);
890
1438
  handlers.onPointerEnter?.(data);
891
1439
  } else if (hoveredItem.stopped) {
@@ -895,9 +1443,10 @@ function createEvents(store) {
895
1443
  handlers.onPointerMove?.(data);
896
1444
  } else if (isDragOver) {
897
1445
  const id = makeId(data);
898
- const hoveredItem = internal.hovered.get(id);
1446
+ const pointerState2 = getPointerState(internal, pointerId);
1447
+ const hoveredItem = pointerState2.hovered.get(id);
899
1448
  if (!hoveredItem) {
900
- internal.hovered.set(id, data);
1449
+ pointerState2.hovered.set(id, data);
901
1450
  handlers.onDragOverEnter?.(data);
902
1451
  } else if (hoveredItem.stopped) {
903
1452
  data.stopPropagation();
@@ -908,18 +1457,18 @@ function createEvents(store) {
908
1457
  } else {
909
1458
  const handler = handlers[name];
910
1459
  if (handler) {
911
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1460
+ if (!isClickEvent || initialHits.includes(eventObject)) {
912
1461
  pointerMissed(
913
1462
  event,
914
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1463
+ internal.interaction.filter((object) => !initialHits.includes(object))
915
1464
  );
916
1465
  handler(data);
917
1466
  }
918
1467
  } else {
919
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1468
+ if (isClickEvent && initialHits.includes(eventObject)) {
920
1469
  pointerMissed(
921
1470
  event,
922
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1471
+ internal.interaction.filter((object) => !initialHits.includes(object))
923
1472
  );
924
1473
  }
925
1474
  }
@@ -928,7 +1477,15 @@ function createEvents(store) {
928
1477
  handleIntersects(hits, event, delta, onIntersect);
929
1478
  };
930
1479
  }
931
- return { handlePointer };
1480
+ function flushDeferredPointers() {
1481
+ const { internal, events } = store.getState();
1482
+ if (!events.frameTimedRaycasts) return;
1483
+ for (const [pointerId, event] of internal.pointerDirty) {
1484
+ processDeferredPointer(event, pointerId);
1485
+ }
1486
+ internal.pointerDirty.clear();
1487
+ }
1488
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
932
1489
  }
933
1490
  const DOM_EVENTS = {
934
1491
  onClick: ["click", false],
@@ -947,11 +1504,16 @@ const DOM_EVENTS = {
947
1504
  onLostPointerCapture: ["lostpointercapture", true]
948
1505
  };
949
1506
  function createPointerEvents(store) {
950
- const { handlePointer } = createEvents(store);
1507
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1508
+ let nextXRPointerId = XR_POINTER_ID_START;
1509
+ const xrPointers = /* @__PURE__ */ new Map();
951
1510
  return {
952
1511
  priority: 1,
953
1512
  enabled: true,
954
- compute(event, state, previous) {
1513
+ frameTimedRaycasts: true,
1514
+ alwaysFireOnScroll: true,
1515
+ updateOnFrame: false,
1516
+ compute(event, state) {
955
1517
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
956
1518
  state.raycaster.setFromCamera(state.pointer, state.camera);
957
1519
  },
@@ -960,11 +1522,33 @@ function createPointerEvents(store) {
960
1522
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
961
1523
  {}
962
1524
  ),
963
- update: () => {
1525
+ update: (pointerId) => {
1526
+ const { events, internal } = store.getState();
1527
+ if (!events.handlers) return;
1528
+ if (pointerId !== void 0) {
1529
+ const event = internal.pointerDirty.get(pointerId);
1530
+ if (event) {
1531
+ internal.pointerDirty.delete(pointerId);
1532
+ processDeferredPointer(event, pointerId);
1533
+ } else if (internal.lastEvent?.current) {
1534
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1535
+ }
1536
+ } else {
1537
+ flushDeferredPointers();
1538
+ if (internal.lastEvent?.current) {
1539
+ events.handlers.onPointerMove(internal.lastEvent.current);
1540
+ }
1541
+ }
1542
+ },
1543
+ flush: () => {
964
1544
  const { events, internal } = store.getState();
965
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1545
+ flushDeferredPointers();
1546
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1547
+ events.handlers.onPointerMove(internal.lastEvent.current);
1548
+ }
966
1549
  },
967
1550
  connect: (target) => {
1551
+ if (!target) return;
968
1552
  const { set, events } = store.getState();
969
1553
  events.disconnect?.();
970
1554
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -988,6 +1572,32 @@ function createPointerEvents(store) {
988
1572
  }
989
1573
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
990
1574
  }
1575
+ },
1576
+ registerPointer: (config) => {
1577
+ const pointerId = nextXRPointerId++;
1578
+ xrPointers.set(pointerId, config);
1579
+ const { internal } = store.getState();
1580
+ getPointerState(internal, pointerId);
1581
+ return pointerId;
1582
+ },
1583
+ unregisterPointer: (pointerId) => {
1584
+ xrPointers.delete(pointerId);
1585
+ const { internal } = store.getState();
1586
+ const pointerState = internal.pointerMap.get(pointerId);
1587
+ if (pointerState) {
1588
+ for (const [, hoveredObj] of pointerState.hovered) {
1589
+ const eventObject = hoveredObj.eventObject;
1590
+ const instance = eventObject.__r3f;
1591
+ if (instance?.eventCount) {
1592
+ const handlers = instance.handlers;
1593
+ const data = { ...hoveredObj, intersections: [] };
1594
+ handlers.onPointerOut?.(data);
1595
+ handlers.onPointerLeave?.(data);
1596
+ }
1597
+ }
1598
+ internal.pointerMap.delete(pointerId);
1599
+ }
1600
+ internal.pointerDirty.delete(pointerId);
991
1601
  }
992
1602
  };
993
1603
  }
@@ -1078,23 +1688,29 @@ const createStore = (invalidate, advance) => {
1078
1688
  set,
1079
1689
  get,
1080
1690
  // Mock objects that have to be configured
1691
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
1692
+ primaryStore: null,
1081
1693
  gl: null,
1082
1694
  renderer: null,
1083
1695
  camera: null,
1084
1696
  frustum: new webgpu.Frustum(),
1085
1697
  autoUpdateFrustum: true,
1086
1698
  raycaster: null,
1087
- events: { priority: 1, enabled: true, connected: false },
1699
+ events: {
1700
+ priority: 1,
1701
+ enabled: true,
1702
+ connected: false,
1703
+ frameTimedRaycasts: true,
1704
+ alwaysFireOnScroll: true,
1705
+ updateOnFrame: false
1706
+ },
1088
1707
  scene: null,
1089
1708
  rootScene: null,
1090
1709
  xr: null,
1091
1710
  inspector: null,
1092
1711
  invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1093
1712
  advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1094
- legacy: false,
1095
- linear: false,
1096
- flat: false,
1097
- textureColorSpace: "srgb",
1713
+ textureColorSpace: webgpu.SRGBColorSpace,
1098
1714
  isLegacy: false,
1099
1715
  webGPUSupported: false,
1100
1716
  isNative: false,
@@ -1134,10 +1750,40 @@ const createStore = (invalidate, advance) => {
1134
1750
  getCurrentViewport
1135
1751
  },
1136
1752
  setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1137
- setSize: (width, height, top = 0, left = 0) => {
1138
- const camera = get().camera;
1139
- const size = { width, height, top, left };
1140
- set((state2) => ({ size, viewport: { ...state2.viewport, ...getCurrentViewport(camera, defaultTarget, size) } }));
1753
+ setSize: (width, height, top, left) => {
1754
+ const state2 = get();
1755
+ if (width === void 0) {
1756
+ set({ _sizeImperative: false });
1757
+ if (state2._sizeProps) {
1758
+ const { width: propW, height: propH } = state2._sizeProps;
1759
+ if (propW !== void 0 || propH !== void 0) {
1760
+ const currentSize = state2.size;
1761
+ const newSize = {
1762
+ width: propW ?? currentSize.width,
1763
+ height: propH ?? currentSize.height,
1764
+ top: currentSize.top,
1765
+ left: currentSize.left
1766
+ };
1767
+ set((s) => ({
1768
+ size: newSize,
1769
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1770
+ }));
1771
+ scheduler.getScheduler().invalidate();
1772
+ }
1773
+ }
1774
+ return;
1775
+ }
1776
+ const w = width;
1777
+ const h = height ?? width;
1778
+ const t = top ?? state2.size.top;
1779
+ const l = left ?? state2.size.left;
1780
+ const size = { width: w, height: h, top: t, left: l };
1781
+ set((s) => ({
1782
+ size,
1783
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1784
+ _sizeImperative: true
1785
+ }));
1786
+ scheduler.getScheduler().invalidate();
1141
1787
  },
1142
1788
  setDpr: (dpr) => set((state2) => {
1143
1789
  const resolved = calculateDpr(dpr);
@@ -1148,22 +1794,32 @@ const createStore = (invalidate, advance) => {
1148
1794
  },
1149
1795
  setError: (error) => set(() => ({ error })),
1150
1796
  error: null,
1151
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1797
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
1152
1798
  uniforms: {},
1153
1799
  nodes: {},
1800
+ buffers: {},
1801
+ gpuStorage: {},
1154
1802
  textures: /* @__PURE__ */ new Map(),
1155
- postProcessing: null,
1803
+ _textureRefs: /* @__PURE__ */ new Map(),
1804
+ renderPipeline: null,
1156
1805
  passes: {},
1806
+ _hmrVersion: 0,
1807
+ _sizeImperative: false,
1808
+ _sizeProps: null,
1157
1809
  previousRoot: void 0,
1158
1810
  internal: {
1159
1811
  // Events
1160
1812
  interaction: [],
1161
- hovered: /* @__PURE__ */ new Map(),
1162
1813
  subscribers: [],
1814
+ // Per-pointer state (new unified structure)
1815
+ pointerMap: /* @__PURE__ */ new Map(),
1816
+ pointerDirty: /* @__PURE__ */ new Map(),
1817
+ lastEvent: React__namespace.createRef(),
1818
+ // Deprecated but kept for backwards compatibility
1819
+ hovered: /* @__PURE__ */ new Map(),
1163
1820
  initialClick: [0, 0],
1164
1821
  initialHits: [],
1165
1822
  capturedMap: /* @__PURE__ */ new Map(),
1166
- lastEvent: React__namespace.createRef(),
1167
1823
  // Visibility tracking (onFramed, onOccluded, onVisible)
1168
1824
  visibilityRegistry: /* @__PURE__ */ new Map(),
1169
1825
  // Occlusion system (WebGPU only)
@@ -1246,13 +1902,22 @@ const createStore = (invalidate, advance) => {
1246
1902
  rootStore.subscribe(() => {
1247
1903
  const { camera, size, viewport, set, internal } = rootStore.getState();
1248
1904
  const actualRenderer = internal.actualRenderer;
1905
+ const canvasTarget = internal.canvasTarget;
1249
1906
  if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1250
1907
  oldSize = size;
1251
1908
  oldDpr = viewport.dpr;
1252
1909
  updateCamera(camera, size);
1253
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1254
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1255
- actualRenderer.setSize(size.width, size.height, updateStyle);
1910
+ if (internal.isSecondary && canvasTarget) {
1911
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1912
+ canvasTarget.setSize(size.width, size.height, false);
1913
+ } else {
1914
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1915
+ actualRenderer.setSize(size.width, size.height, false);
1916
+ if (canvasTarget) {
1917
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1918
+ canvasTarget.setSize(size.width, size.height, false);
1919
+ }
1920
+ }
1256
1921
  }
1257
1922
  if (camera !== oldCamera) {
1258
1923
  oldCamera = camera;
@@ -1323,1076 +1988,45 @@ useLoader.clear = function(loader, input) {
1323
1988
  };
1324
1989
  useLoader.loader = getLoader;
1325
1990
 
1326
- var __defProp$1 = Object.defineProperty;
1327
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1328
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1329
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1330
- class PhaseGraph {
1331
- constructor() {
1332
- /** Ordered list of phase nodes */
1333
- __publicField$1(this, "phases", []);
1334
- /** Quick lookup by name */
1335
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1336
- /** Cached ordered names (invalidated on changes) */
1337
- __publicField$1(this, "orderedNamesCache", null);
1338
- this.initializeDefaultPhases();
1339
- }
1340
- //* Initialization --------------------------------
1341
- initializeDefaultPhases() {
1342
- for (const name of DEFAULT_PHASES) {
1343
- const node = { name, isAutoGenerated: false };
1344
- this.phases.push(node);
1345
- this.phaseMap.set(name, node);
1346
- }
1347
- this.invalidateCache();
1348
- }
1349
- //* Public API --------------------------------
1350
- /**
1351
- * Add a named phase to the graph
1352
- * @param name - Phase name (must be unique)
1353
- * @param options - Position options (before or after another phase)
1354
- */
1355
- addPhase(name, options = {}) {
1356
- if (this.phaseMap.has(name)) {
1357
- console.warn(`[useFrame] Phase "${name}" already exists`);
1358
- return;
1359
- }
1360
- const { before, after } = options;
1361
- const node = { name, isAutoGenerated: false };
1362
- let insertIndex = this.phases.length;
1363
- const targetIndex = this.getPhaseIndex(before ?? after);
1364
- if (targetIndex !== -1) insertIndex = before ? targetIndex : targetIndex + 1;
1365
- else {
1366
- const constraintType = before ? "before" : "after";
1367
- console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
1368
- }
1369
- this.phases.splice(insertIndex, 0, node);
1370
- this.phaseMap.set(name, node);
1371
- this.invalidateCache();
1372
- }
1373
- /**
1374
- * Get ordered list of phase names
1375
- */
1376
- getOrderedPhases() {
1377
- if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
1378
- return this.orderedNamesCache;
1379
- }
1380
- /**
1381
- * Check if a phase exists
1382
- */
1383
- hasPhase(name) {
1384
- return this.phaseMap.has(name);
1385
- }
1386
- /**
1387
- * Get the index of a phase (-1 if not found)
1388
- */
1389
- getPhaseIndex(name) {
1390
- if (!name) return -1;
1391
- return this.phases.findIndex((p) => p.name === name);
1392
- }
1393
- /**
1394
- * Ensure a phase exists, creating an auto-generated one if needed.
1395
- * Used for resolving before/after constraints.
1396
- *
1397
- * @param name - The phase name to ensure exists
1398
- * @returns The phase name (may be auto-generated like 'before:render')
1399
- */
1400
- ensurePhase(name) {
1401
- if (this.phaseMap.has(name)) return name;
1402
- const node = { name, isAutoGenerated: true };
1403
- this.phases.push(node);
1404
- this.phaseMap.set(name, node);
1405
- this.invalidateCache();
1406
- return name;
1407
- }
1408
- /**
1409
- * Resolve where a job with before/after constraints should go.
1410
- * Creates auto-generated phases if needed.
1411
- *
1412
- * @param before - Phase(s) to run before
1413
- * @param after - Phase(s) to run after
1414
- * @returns The resolved phase name
1415
- */
1416
- resolveConstraintPhase(before, after) {
1417
- const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
1418
- const afterArr = after ? Array.isArray(after) ? after : [after] : [];
1419
- if (beforeArr.length > 0) {
1420
- return this.ensureAutoPhase(beforeArr[0], "before", 0);
1421
- }
1422
- if (afterArr.length > 0) {
1423
- return this.ensureAutoPhase(afterArr[0], "after", 1);
1424
- }
1425
- return "update";
1426
- }
1427
- /**
1428
- * Ensure an auto-generated phase exists relative to a target phase.
1429
- * Creates the phase if it doesn't exist, inserting it at the correct position.
1430
- *
1431
- * @param target - The target phase name to position relative to
1432
- * @param prefix - Prefix for auto-generated phase name ('before' or 'after')
1433
- * @param offset - Insertion offset (0 for before, 1 for after)
1434
- * @returns The auto-generated phase name
1435
- */
1436
- ensureAutoPhase(target, prefix, offset) {
1437
- const autoName = `${prefix}:${target}`;
1438
- if (this.phaseMap.has(autoName)) return autoName;
1439
- const node = { name: autoName, isAutoGenerated: true };
1440
- const targetIndex = this.getPhaseIndex(target);
1441
- if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
1442
- else this.phases.push(node);
1443
- this.phaseMap.set(autoName, node);
1444
- this.invalidateCache();
1445
- return autoName;
1446
- }
1447
- // Internal --------------------------------
1448
- invalidateCache() {
1449
- this.orderedNamesCache = null;
1450
- }
1451
- }
1452
-
1453
- function rebuildSortedJobs(jobs, phaseGraph) {
1454
- const orderedPhases = phaseGraph.getOrderedPhases();
1455
- const buckets = /* @__PURE__ */ new Map();
1456
- for (const phase of orderedPhases) {
1457
- buckets.set(phase, []);
1458
- }
1459
- for (const job of jobs.values()) {
1460
- if (!job.enabled) continue;
1461
- let bucket = buckets.get(job.phase);
1462
- if (!bucket) {
1463
- bucket = [];
1464
- buckets.set(job.phase, bucket);
1465
- }
1466
- bucket.push(job);
1467
- }
1468
- const sortedBuckets = [];
1469
- for (const phase of orderedPhases) {
1470
- const bucket = buckets.get(phase);
1471
- if (!bucket || bucket.length === 0) continue;
1472
- bucket.sort((a, b) => {
1473
- if (a.priority !== b.priority) return b.priority - a.priority;
1474
- return a.index - b.index;
1475
- });
1476
- sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
1477
- }
1478
- for (const [phase, bucket] of buckets) {
1479
- if (!orderedPhases.includes(phase) && bucket.length > 0) {
1480
- bucket.sort((a, b) => {
1481
- if (a.priority !== b.priority) return b.priority - a.priority;
1482
- return a.index - b.index;
1483
- });
1484
- sortedBuckets.push(bucket);
1485
- }
1486
- }
1487
- return sortedBuckets.flat();
1488
- }
1489
- function hasCrossJobConstraints(bucket) {
1490
- const jobIds = new Set(bucket.map((j) => j.id));
1491
- for (const job of bucket) {
1492
- for (const ref of job.before) {
1493
- if (jobIds.has(ref)) return true;
1494
- }
1495
- for (const ref of job.after) {
1496
- if (jobIds.has(ref)) return true;
1497
- }
1498
- }
1499
- return false;
1500
- }
1501
- function topologicalSort(jobs) {
1502
- const n = jobs.length;
1503
- if (n <= 1) return jobs;
1504
- const jobMap = /* @__PURE__ */ new Map();
1505
- const inDegree = /* @__PURE__ */ new Map();
1506
- const adjacency = /* @__PURE__ */ new Map();
1507
- for (const job of jobs) {
1508
- jobMap.set(job.id, job);
1509
- inDegree.set(job.id, 0);
1510
- adjacency.set(job.id, []);
1511
- }
1512
- for (const job of jobs) {
1513
- for (const ref of job.before) {
1514
- if (jobMap.has(ref)) {
1515
- adjacency.get(job.id).push(ref);
1516
- inDegree.set(ref, inDegree.get(ref) + 1);
1517
- }
1518
- }
1519
- for (const ref of job.after) {
1520
- if (jobMap.has(ref)) {
1521
- adjacency.get(ref).push(job.id);
1522
- inDegree.set(job.id, inDegree.get(job.id) + 1);
1523
- }
1524
- }
1525
- }
1526
- const queue = [];
1527
- for (const job of jobs) {
1528
- if (inDegree.get(job.id) === 0) {
1529
- queue.push(job);
1530
- }
1531
- }
1532
- queue.sort((a, b) => {
1533
- if (a.priority !== b.priority) return b.priority - a.priority;
1534
- return a.index - b.index;
1535
- });
1536
- const result = [];
1537
- while (queue.length > 0) {
1538
- const job = queue.shift();
1539
- result.push(job);
1540
- const neighbors = adjacency.get(job.id) || [];
1541
- for (const neighborId of neighbors) {
1542
- const newDegree = inDegree.get(neighborId) - 1;
1543
- inDegree.set(neighborId, newDegree);
1544
- if (newDegree === 0) {
1545
- const neighbor = jobMap.get(neighborId);
1546
- insertSorted(queue, neighbor);
1547
- }
1548
- }
1549
- }
1550
- if (result.length !== n) {
1551
- console.warn("[useFrame] Circular dependency detected in job constraints");
1552
- const resultIds = new Set(result.map((j) => j.id));
1553
- for (const job of jobs) {
1554
- if (!resultIds.has(job.id)) result.push(job);
1555
- }
1556
- }
1557
- return result;
1558
- }
1559
- function insertSorted(arr, job) {
1560
- let i = 0;
1561
- while (i < arr.length) {
1562
- const cmp = arr[i];
1563
- if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
1564
- break;
1565
- }
1566
- i++;
1567
- }
1568
- arr.splice(i, 0, job);
1569
- }
1570
-
1571
- function shouldRun(job, now) {
1572
- if (!job.enabled) return false;
1573
- if (!job.fps) return true;
1574
- const minInterval = 1e3 / job.fps;
1575
- const lastRun = job.lastRun ?? 0;
1576
- const elapsed = now - lastRun;
1577
- if (elapsed < minInterval) return false;
1578
- if (job.drop) {
1579
- job.lastRun = now;
1580
- } else {
1581
- const steps = Math.floor(elapsed / minInterval);
1582
- job.lastRun = lastRun + steps * minInterval;
1583
- if (job.lastRun < now - minInterval) {
1584
- job.lastRun = now;
1585
- }
1586
- }
1587
- return true;
1588
- }
1589
- function resetJobTiming(job) {
1590
- job.lastRun = void 0;
1591
- }
1592
-
1593
- var __defProp = Object.defineProperty;
1594
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1595
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1596
- const hmrData = (() => {
1597
- if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
1598
- if (typeof import_meta_hot !== "undefined") return import_meta_hot;
1599
- try {
1600
- return (0, eval)("import.meta.hot");
1601
- } catch {
1602
- return void 0;
1603
- }
1604
- })();
1605
- const _Scheduler = class _Scheduler {
1606
- //* Constructor ================================
1607
- constructor() {
1608
- //* Critical State ================================
1609
- __publicField(this, "roots", /* @__PURE__ */ new Map());
1610
- __publicField(this, "phaseGraph");
1611
- __publicField(this, "loopState", {
1612
- running: false,
1613
- rafHandle: null,
1614
- lastTime: null,
1615
- // null = uninitialized, 0+ = valid timestamp
1616
- frameCount: 0,
1617
- elapsedTime: 0,
1618
- createdAt: performance.now()
1619
- });
1620
- __publicField(this, "stoppedTime", 0);
1621
- //* Private State ================================
1622
- __publicField(this, "nextRootIndex", 0);
1623
- __publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
1624
- __publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
1625
- __publicField(this, "nextGlobalIndex", 0);
1626
- __publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
1627
- __publicField(this, "nextJobIndex", 0);
1628
- __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1629
- __publicField(this, "pendingFrames", 0);
1630
- __publicField(this, "_frameloop", "always");
1631
- //* Independent Mode & Error Handling State ================================
1632
- __publicField(this, "_independent", false);
1633
- __publicField(this, "errorHandler", null);
1634
- __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1635
- //* Core Loop Execution Methods ================================
1636
- /**
1637
- * Main RAF loop callback.
1638
- * Executes frame, handles demand mode, and schedules next frame.
1639
- * @param {number} timestamp - RAF timestamp in milliseconds
1640
- * @returns {void}
1641
- * @private
1642
- */
1643
- __publicField(this, "loop", (timestamp) => {
1644
- if (!this.loopState.running) return;
1645
- this.executeFrame(timestamp);
1646
- if (this._frameloop === "demand") {
1647
- this.pendingFrames = Math.max(0, this.pendingFrames - 1);
1648
- if (this.pendingFrames === 0) {
1649
- this.notifyIdle(timestamp);
1650
- return this.stop();
1651
- }
1652
- }
1653
- this.loopState.rafHandle = requestAnimationFrame(this.loop);
1654
- });
1655
- this.phaseGraph = new PhaseGraph();
1656
- }
1657
- static get instance() {
1658
- return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1659
- }
1660
- static set instance(value) {
1661
- globalThis[_Scheduler.INSTANCE_KEY] = value;
1662
- }
1663
- /**
1664
- * Get the global scheduler instance (creates if doesn't exist).
1665
- * Uses HMR data to preserve instance across hot reloads.
1666
- * @returns {Scheduler} The singleton scheduler instance
1667
- */
1668
- static get() {
1669
- if (!_Scheduler.instance && hmrData?.data?.scheduler) {
1670
- _Scheduler.instance = hmrData.data.scheduler;
1671
- }
1672
- if (!_Scheduler.instance) {
1673
- _Scheduler.instance = new _Scheduler();
1674
- if (hmrData?.data) {
1675
- hmrData.data.scheduler = _Scheduler.instance;
1676
- }
1677
- }
1678
- return _Scheduler.instance;
1679
- }
1680
- /**
1681
- * Reset the singleton instance. Stops the loop and clears all state.
1682
- * Primarily used for testing to ensure clean state between tests.
1683
- * @returns {void}
1684
- */
1685
- static reset() {
1686
- if (_Scheduler.instance) {
1687
- _Scheduler.instance.stop();
1688
- _Scheduler.instance = null;
1689
- }
1690
- if (hmrData?.data) {
1691
- hmrData.data.scheduler = null;
1692
- }
1693
- }
1694
- //* Getters & Setters ================================
1695
- get phases() {
1696
- return this.phaseGraph.getOrderedPhases();
1697
- }
1698
- get frameloop() {
1699
- return this._frameloop;
1700
- }
1701
- set frameloop(mode) {
1702
- if (this._frameloop === mode) return;
1703
- const wasAlways = this._frameloop === "always";
1704
- this._frameloop = mode;
1705
- if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
1706
- else if (mode !== "always" && wasAlways) this.stop();
1707
- }
1708
- get isRunning() {
1709
- return this.loopState.running;
1710
- }
1711
- get isReady() {
1712
- return this.roots.size > 0;
1713
- }
1714
- get independent() {
1715
- return this._independent;
1716
- }
1717
- set independent(value) {
1718
- this._independent = value;
1719
- if (value) this.ensureDefaultRoot();
1720
- }
1721
- //* Root Management Methods ================================
1722
- /**
1723
- * Register a root (Canvas) with the scheduler.
1724
- * The first root to register starts the RAF loop (if frameloop='always').
1725
- * @param {string} id - Unique identifier for this root
1726
- * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1727
- * @returns {() => void} Unsubscribe function to remove this root
1728
- */
1729
- registerRoot(id, options = {}) {
1730
- if (this.roots.has(id)) {
1731
- console.warn(`[Scheduler] Root "${id}" already registered`);
1732
- return () => this.unregisterRoot(id);
1733
- }
1734
- const entry = {
1735
- id,
1736
- getState: options.getState ?? (() => ({})),
1737
- jobs: /* @__PURE__ */ new Map(),
1738
- sortedJobs: [],
1739
- needsRebuild: false
1740
- };
1741
- if (options.onError) {
1742
- this.errorHandler = options.onError;
1743
- }
1744
- this.roots.set(id, entry);
1745
- if (this.roots.size === 1) {
1746
- this.notifyRootReady();
1747
- if (this._frameloop === "always") this.start();
1748
- }
1749
- return () => this.unregisterRoot(id);
1750
- }
1751
- /**
1752
- * Unregister a root from the scheduler.
1753
- * Cleans up all job state listeners for this root's jobs.
1754
- * The last root to unregister stops the RAF loop.
1755
- * @param {string} id - The root ID to unregister
1756
- * @returns {void}
1757
- */
1758
- unregisterRoot(id) {
1759
- const root = this.roots.get(id);
1760
- if (!root) return;
1761
- for (const jobId of root.jobs.keys()) {
1762
- this.jobStateListeners.delete(jobId);
1763
- }
1764
- this.roots.delete(id);
1765
- if (this.roots.size === 0) {
1766
- this.stop();
1767
- this.errorHandler = null;
1768
- }
1769
- }
1770
- /**
1771
- * Subscribe to be notified when a root becomes available.
1772
- * Fires immediately if a root already exists.
1773
- * @param {() => void} callback - Function called when first root registers
1774
- * @returns {() => void} Unsubscribe function
1775
- */
1776
- onRootReady(callback) {
1777
- if (this.roots.size > 0) {
1778
- callback();
1779
- return () => {
1780
- };
1781
- }
1782
- this.rootReadyCallbacks.add(callback);
1783
- return () => this.rootReadyCallbacks.delete(callback);
1784
- }
1785
- /**
1786
- * Notify all registered root-ready callbacks.
1787
- * Called when the first root registers.
1788
- * @returns {void}
1789
- * @private
1790
- */
1791
- notifyRootReady() {
1792
- for (const cb of this.rootReadyCallbacks) {
1793
- try {
1794
- cb();
1795
- } catch (error) {
1796
- console.error("[Scheduler] Error in root-ready callback:", error);
1797
- }
1798
- }
1799
- this.rootReadyCallbacks.clear();
1800
- }
1801
- /**
1802
- * Ensure a default root exists for independent mode.
1803
- * Creates a minimal root with no state provider.
1804
- * @returns {void}
1805
- * @private
1806
- */
1807
- ensureDefaultRoot() {
1808
- if (!this.roots.has("__default__")) {
1809
- this.registerRoot("__default__");
1810
- }
1811
- }
1812
- /**
1813
- * Trigger error handling for job errors.
1814
- * Uses the bound error handler if available, otherwise logs to console.
1815
- * @param {Error} error - The error to handle
1816
- * @returns {void}
1817
- */
1818
- triggerError(error) {
1819
- if (this.errorHandler) this.errorHandler(error);
1820
- else console.error("[Scheduler]", error);
1821
- }
1822
- //* Phase Management Methods ================================
1823
- /**
1824
- * Add a named phase to the scheduler's execution order.
1825
- * Marks all roots for rebuild to incorporate the new phase.
1826
- * @param {string} name - The phase name (e.g., 'physics', 'postprocess')
1827
- * @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
1828
- * @returns {void}
1829
- * @example
1830
- * scheduler.addPhase('physics', { before: 'update' });
1831
- * scheduler.addPhase('postprocess', { after: 'render' });
1832
- */
1833
- addPhase(name, options) {
1834
- this.phaseGraph.addPhase(name, options);
1835
- for (const root of this.roots.values()) {
1836
- root.needsRebuild = true;
1837
- }
1838
- }
1839
- /**
1840
- * Check if a phase exists in the scheduler.
1841
- * @param {string} name - The phase name to check
1842
- * @returns {boolean} True if the phase exists
1843
- */
1844
- hasPhase(name) {
1845
- return this.phaseGraph.hasPhase(name);
1846
- }
1847
- //* Global Job Registration Methods (Deprecated APIs) ================================
1848
- /**
1849
- * Register a global job that runs once per frame (not per-root).
1850
- * Used internally by deprecated addEffect/addAfterEffect APIs.
1851
- * @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
1852
- * @param {string} id - Unique identifier for this global job
1853
- * @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
1854
- * @returns {() => void} Unsubscribe function to remove this global job
1855
- * @deprecated Use useFrame with phases instead
1856
- */
1857
- registerGlobal(phase, id, callback) {
1858
- const job = { id, callback };
1859
- if (phase === "before") {
1860
- this.globalBeforeJobs.set(id, job);
1861
- } else {
1862
- this.globalAfterJobs.set(id, job);
1863
- }
1864
- return () => {
1865
- if (phase === "before") this.globalBeforeJobs.delete(id);
1866
- else this.globalAfterJobs.delete(id);
1867
- };
1868
- }
1869
- //* Idle Callback Methods (Deprecated API) ================================
1870
- /**
1871
- * Register an idle callback that fires when the loop stops.
1872
- * Used internally by deprecated addTail API.
1873
- * @param {(timestamp: number) => void} callback - Function called when loop becomes idle
1874
- * @returns {() => void} Unsubscribe function to remove this idle callback
1875
- * @deprecated Use demand mode with invalidate() instead
1876
- */
1877
- onIdle(callback) {
1878
- this.idleCallbacks.add(callback);
1879
- return () => this.idleCallbacks.delete(callback);
1880
- }
1881
- /**
1882
- * Notify all registered idle callbacks.
1883
- * Called when the loop stops in demand mode.
1884
- * @param {number} timestamp - The RAF timestamp when idle occurred
1885
- * @returns {void}
1886
- * @private
1887
- */
1888
- notifyIdle(timestamp) {
1889
- for (const cb of this.idleCallbacks) {
1890
- try {
1891
- cb(timestamp);
1892
- } catch (error) {
1893
- console.error("[Scheduler] Error in idle callback:", error);
1894
- }
1895
- }
1896
- }
1897
- //* Job Registration & Management Methods ================================
1898
- /**
1899
- * Register a job (frame callback) with a specific root.
1900
- * This is the core registration method used by useFrame internally.
1901
- * @param {FrameNextCallback} callback - The function to call each frame
1902
- * @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
1903
- * @param {string} [options.rootId] - Target root ID (defaults to first registered root)
1904
- * @param {string} [options.id] - Unique job ID (auto-generated if not provided)
1905
- * @param {string} [options.phase] - Execution phase (defaults to 'update')
1906
- * @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
1907
- * @param {number} [options.fps] - FPS throttle limit
1908
- * @param {boolean} [options.drop] - Drop frames when behind (default true)
1909
- * @param {boolean} [options.enabled] - Whether job is active (default true)
1910
- * @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
1911
- * @returns {() => void} Unsubscribe function to remove this job
1912
- */
1913
- register(callback, options = {}) {
1914
- const rootId = options.rootId;
1915
- const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
1916
- if (!root) {
1917
- console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
1918
- return () => {
1919
- };
1920
- }
1921
- const id = options.id ?? this.generateJobId();
1922
- let phase = options.phase ?? "update";
1923
- if (!options.phase && (options.before || options.after)) {
1924
- phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
1925
- }
1926
- const before = this.normalizeConstraints(options.before);
1927
- const after = this.normalizeConstraints(options.after);
1928
- const job = {
1929
- id,
1930
- callback,
1931
- phase,
1932
- before,
1933
- after,
1934
- priority: options.priority ?? 0,
1935
- index: this.nextJobIndex++,
1936
- fps: options.fps,
1937
- drop: options.drop ?? true,
1938
- enabled: options.enabled ?? true,
1939
- system: options.system ?? false
1940
- };
1941
- if (root.jobs.has(id)) {
1942
- console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
1943
- }
1944
- root.jobs.set(id, job);
1945
- root.needsRebuild = true;
1946
- return () => this.unregister(id, root.id);
1947
- }
1948
- /**
1949
- * Unregister a job by its ID.
1950
- * Searches all roots if rootId is not provided.
1951
- * @param {string} id - The job ID to unregister
1952
- * @param {string} [rootId] - Optional root ID to search (searches all if not provided)
1953
- * @returns {void}
1954
- */
1955
- unregister(id, rootId) {
1956
- const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
1957
- if (root?.jobs.delete(id)) {
1958
- root.needsRebuild = true;
1959
- this.jobStateListeners.delete(id);
1960
- }
1961
- }
1962
- /**
1963
- * Update a job's options dynamically.
1964
- * Searches all roots to find the job by ID.
1965
- * Phase/constraint changes trigger a rebuild of the sorted job list.
1966
- * @param {string} id - The job ID to update
1967
- * @param {Partial<JobOptions>} options - The options to update
1968
- * @returns {void}
1969
- */
1970
- updateJob(id, options) {
1971
- let job;
1972
- let root;
1973
- for (const r of this.roots.values()) {
1974
- job = r.jobs.get(id);
1975
- if (job) {
1976
- root = r;
1977
- break;
1978
- }
1979
- }
1980
- if (!job || !root) return;
1981
- if (options.priority !== void 0) job.priority = options.priority;
1982
- if (options.fps !== void 0) job.fps = options.fps;
1983
- if (options.drop !== void 0) job.drop = options.drop;
1984
- if (options.enabled !== void 0) {
1985
- const wasEnabled = job.enabled;
1986
- job.enabled = options.enabled;
1987
- if (!wasEnabled && job.enabled) resetJobTiming(job);
1988
- if (wasEnabled !== job.enabled) root.needsRebuild = true;
1989
- }
1990
- if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
1991
- if (options.phase) job.phase = options.phase;
1992
- if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
1993
- if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
1994
- root.needsRebuild = true;
1995
- }
1996
- }
1997
- //* Job State Management Methods ================================
1998
- /**
1999
- * Check if a job is currently paused (disabled).
2000
- * @param {string} id - The job ID to check
2001
- * @returns {boolean} True if the job exists and is paused
2002
- */
2003
- isJobPaused(id) {
2004
- for (const root of this.roots.values()) {
2005
- const job = root.jobs.get(id);
2006
- if (job) return !job.enabled;
2007
- }
2008
- return false;
2009
- }
2010
- /**
2011
- * Subscribe to state changes for a specific job.
2012
- * Listener is called when job is paused or resumed.
2013
- * @param {string} id - The job ID to subscribe to
2014
- * @param {() => void} listener - Callback invoked on state changes
2015
- * @returns {() => void} Unsubscribe function
2016
- */
2017
- subscribeJobState(id, listener) {
2018
- if (!this.jobStateListeners.has(id)) {
2019
- this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
2020
- }
2021
- this.jobStateListeners.get(id).add(listener);
2022
- return () => {
2023
- this.jobStateListeners.get(id)?.delete(listener);
2024
- if (this.jobStateListeners.get(id)?.size === 0) {
2025
- this.jobStateListeners.delete(id);
2026
- }
2027
- };
2028
- }
2029
- /**
2030
- * Notify all listeners that a job's state has changed.
2031
- * @param {string} id - The job ID that changed
2032
- * @returns {void}
2033
- * @private
2034
- */
2035
- notifyJobStateChange(id) {
2036
- this.jobStateListeners.get(id)?.forEach((listener) => listener());
2037
- }
2038
- /**
2039
- * Pause a job by ID (sets enabled=false).
2040
- * Notifies any subscribed state listeners.
2041
- * @param {string} id - The job ID to pause
2042
- * @returns {void}
2043
- */
2044
- pauseJob(id) {
2045
- this.updateJob(id, { enabled: false });
2046
- this.notifyJobStateChange(id);
2047
- }
2048
- /**
2049
- * Resume a paused job by ID (sets enabled=true).
2050
- * Resets job timing to prevent frame accumulation.
2051
- * Notifies any subscribed state listeners.
2052
- * @param {string} id - The job ID to resume
2053
- * @returns {void}
2054
- */
2055
- resumeJob(id) {
2056
- this.updateJob(id, { enabled: true });
2057
- this.notifyJobStateChange(id);
2058
- }
2059
- //* Frame Loop Control Methods ================================
2060
- /**
2061
- * Start the requestAnimationFrame loop.
2062
- * Resets timing state (elapsedTime, frameCount) on start.
2063
- * No-op if already running.
2064
- * @returns {void}
2065
- */
2066
- start() {
2067
- if (this.loopState.running) return;
2068
- const { elapsedTime, createdAt } = this.loopState;
2069
- let adjustedCreated = 0;
2070
- if (this.stoppedTime > 0) {
2071
- adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
2072
- this.stoppedTime = 0;
2073
- }
2074
- Object.assign(this.loopState, {
2075
- running: true,
2076
- elapsedTime: elapsedTime ?? 0,
2077
- lastTime: performance.now(),
2078
- createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
2079
- frameCount: 0,
2080
- rafHandle: requestAnimationFrame(this.loop)
2081
- });
2082
- }
2083
- /**
2084
- * Stop the requestAnimationFrame loop.
2085
- * Cancels any pending RAF callback.
2086
- * No-op if not running.
2087
- * @returns {void}
2088
- */
2089
- stop() {
2090
- if (!this.loopState.running) return;
2091
- this.loopState.running = false;
2092
- if (this.loopState.rafHandle !== null) {
2093
- cancelAnimationFrame(this.loopState.rafHandle);
2094
- this.loopState.rafHandle = null;
2095
- }
2096
- this.stoppedTime = performance.now();
2097
- }
2098
- /**
2099
- * Request frames to be rendered in demand mode.
2100
- * Accumulates pending frames (capped at 60) and starts the loop if not running.
2101
- * No-op if frameloop is not 'demand'.
2102
- * @param {number} [frames=1] - Number of frames to request
2103
- * @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
2104
- * - `false` (default): Sets pending frames to the specified value (replaces existing count)
2105
- * - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
2106
- * @returns {void}
2107
- * @example
2108
- * // Request a single frame render
2109
- * scheduler.invalidate();
2110
- *
2111
- * @example
2112
- * // Request 5 frames (e.g., for animations)
2113
- * scheduler.invalidate(5);
2114
- *
2115
- * @example
2116
- * // Set pending frames to exactly 3 (don't stack with existing)
2117
- * scheduler.invalidate(3, false);
2118
- *
2119
- * @example
2120
- * // Add 2 more frames to existing pending count
2121
- * scheduler.invalidate(2, true);
2122
- */
2123
- invalidate(frames = 1, stackFrames = false) {
2124
- if (this._frameloop !== "demand") return;
2125
- const baseFrames = stackFrames ? this.pendingFrames : 0;
2126
- this.pendingFrames = Math.min(60, baseFrames + frames);
2127
- if (!this.loopState.running && this.pendingFrames > 0) this.start();
2128
- }
2129
- /**
2130
- * Reset timing state for deterministic testing.
2131
- * Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
2132
- * @returns {void}
2133
- */
2134
- resetTiming() {
2135
- this.loopState.lastTime = null;
2136
- this.loopState.frameCount = 0;
2137
- this.loopState.elapsedTime = 0;
2138
- this.loopState.createdAt = performance.now();
2139
- }
2140
- //* Manual Stepping Methods ================================
2141
- /**
2142
- * Manually execute a single frame for all roots.
2143
- * Useful for frameloop='never' mode or testing scenarios.
2144
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2145
- * @returns {void}
2146
- * @example
2147
- * // Manual control mode
2148
- * scheduler.frameloop = 'never';
2149
- * scheduler.step(); // Execute one frame
2150
- */
2151
- step(timestamp) {
2152
- const now = timestamp ?? performance.now();
2153
- this.executeFrame(now);
2154
- }
2155
- /**
2156
- * Manually execute a single job by its ID.
2157
- * Useful for testing individual job callbacks in isolation.
2158
- * @param {string} id - The job ID to step
2159
- * @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
2160
- * @returns {void}
2161
- */
2162
- stepJob(id, timestamp) {
2163
- let job;
2164
- let root;
2165
- for (const r of this.roots.values()) {
2166
- job = r.jobs.get(id);
2167
- if (job) {
2168
- root = r;
2169
- break;
2170
- }
2171
- }
2172
- if (!job || !root) {
2173
- console.warn(`[Scheduler] Job "${id}" not found`);
2174
- return;
2175
- }
2176
- const now = timestamp ?? performance.now();
2177
- const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
2178
- const delta = deltaMs / 1e3;
2179
- const elapsed = now - this.loopState.createdAt;
2180
- const providedState = root.getState?.() ?? {};
2181
- const frameState = {
2182
- ...providedState,
2183
- time: now,
2184
- delta,
2185
- elapsed,
2186
- frame: this.loopState.frameCount
2187
- };
2188
- try {
2189
- job.callback(frameState, delta);
2190
- } catch (error) {
2191
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2192
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2193
- }
2194
- }
2195
- /**
2196
- * Execute a single frame across all roots.
2197
- * Order: globalBefore → each root's jobs → globalAfter
2198
- * @param {number} timestamp - RAF timestamp in milliseconds
2199
- * @returns {void}
2200
- * @private
2201
- */
2202
- executeFrame(timestamp) {
2203
- const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
2204
- const delta = deltaMs / 1e3;
2205
- this.loopState.lastTime = timestamp;
2206
- this.loopState.frameCount++;
2207
- this.loopState.elapsedTime += deltaMs;
2208
- this.runGlobalJobs(this.globalBeforeJobs, timestamp);
2209
- for (const root of this.roots.values()) {
2210
- this.tickRoot(root, timestamp, delta);
2211
- }
2212
- this.runGlobalJobs(this.globalAfterJobs, timestamp);
2213
- }
2214
- /**
2215
- * Run all global jobs from a job map.
2216
- * Catches and logs errors without stopping execution.
2217
- * @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
2218
- * @param {number} timestamp - RAF timestamp in milliseconds
2219
- * @returns {void}
2220
- * @private
2221
- */
2222
- runGlobalJobs(jobs, timestamp) {
2223
- for (const job of jobs.values()) {
2224
- try {
2225
- job.callback(timestamp);
2226
- } catch (error) {
2227
- console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2228
- }
2229
- }
2230
- }
2231
- /**
2232
- * Execute all jobs for a single root in sorted order.
2233
- * Rebuilds sorted job list if needed, then dispatches each job.
2234
- * Errors are caught and propagated via triggerError.
2235
- * @param {RootEntry} root - The root entry to tick
2236
- * @param {number} timestamp - RAF timestamp in milliseconds
2237
- * @param {number} delta - Time since last frame in seconds
2238
- * @returns {void}
2239
- * @private
2240
- */
2241
- tickRoot(root, timestamp, delta) {
2242
- if (root.needsRebuild) {
2243
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2244
- root.needsRebuild = false;
2245
- }
2246
- const providedState = root.getState?.() ?? {};
2247
- const frameState = {
2248
- ...providedState,
2249
- time: timestamp,
2250
- delta,
2251
- elapsed: this.loopState.elapsedTime / 1e3,
2252
- // Convert ms to seconds
2253
- frame: this.loopState.frameCount
2254
- };
2255
- for (const job of root.sortedJobs) {
2256
- if (!shouldRun(job, timestamp)) continue;
2257
- try {
2258
- job.callback(frameState, delta);
2259
- } catch (error) {
2260
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2261
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2262
- }
2263
- }
2264
- }
2265
- //* Debug & Inspection Methods ================================
2266
- /**
2267
- * Get the total number of registered jobs across all roots.
2268
- * Includes both per-root jobs and global before/after jobs.
2269
- * @returns {number} Total job count
2270
- */
2271
- getJobCount() {
2272
- let count = 0;
2273
- for (const root of this.roots.values()) {
2274
- count += root.jobs.size;
2275
- }
2276
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2277
- }
2278
- /**
2279
- * Get all registered job IDs across all roots.
2280
- * Includes both per-root jobs and global before/after jobs.
2281
- * @returns {string[]} Array of all job IDs
2282
- */
2283
- getJobIds() {
2284
- const ids = [];
2285
- for (const root of this.roots.values()) {
2286
- ids.push(...root.jobs.keys());
2287
- }
2288
- ids.push(...this.globalBeforeJobs.keys());
2289
- ids.push(...this.globalAfterJobs.keys());
2290
- return ids;
2291
- }
2292
- /**
2293
- * Get the number of registered roots (Canvas instances).
2294
- * @returns {number} Number of registered roots
2295
- */
2296
- getRootCount() {
2297
- return this.roots.size;
2298
- }
2299
- /**
2300
- * Check if any user (non-system) jobs are registered in a specific phase.
2301
- * Used by the default render job to know if a user has taken over rendering.
2302
- *
2303
- * @param phase The phase to check
2304
- * @param rootId Optional root ID to check (checks all roots if not provided)
2305
- * @returns true if any user jobs exist in the phase
2306
- */
2307
- hasUserJobsInPhase(phase, rootId) {
2308
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2309
- return rootsToCheck.some((root) => {
2310
- if (!root) return false;
2311
- for (const job of root.jobs.values()) {
2312
- if (job.phase === phase && !job.system && job.enabled) return true;
2313
- }
2314
- return false;
2315
- });
2316
- }
2317
- //* Utility Methods ================================
2318
- /**
2319
- * Generate a unique root ID for automatic root registration.
2320
- * @returns {string} A unique root ID in the format 'root_N'
2321
- */
2322
- generateRootId() {
2323
- return `root_${this.nextRootIndex++}`;
2324
- }
2325
- /**
2326
- * Generate a unique job ID.
2327
- * @returns {string} A unique job ID in the format 'job_N'
2328
- * @private
2329
- */
2330
- generateJobId() {
2331
- return `job_${this.nextJobIndex}`;
2332
- }
2333
- /**
2334
- * Normalize before/after constraints to a Set.
2335
- * Handles undefined, single string, or array inputs.
2336
- * @param {string | string[] | undefined} value - The constraint value(s)
2337
- * @returns {Set<string>} Normalized Set of constraint strings
2338
- * @private
2339
- */
2340
- normalizeConstraints(value) {
2341
- if (!value) return /* @__PURE__ */ new Set();
2342
- if (Array.isArray(value)) return new Set(value);
2343
- return /* @__PURE__ */ new Set([value]);
2344
- }
2345
- };
2346
- //* Static State & Methods (Singleton Usage) ================================
2347
- //* Cross-Bundle Singleton Key ==============================
2348
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2349
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2350
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2351
- let Scheduler = _Scheduler;
2352
- const getScheduler = () => Scheduler.get();
2353
- if (hmrData) {
2354
- hmrData.accept?.();
2355
- }
2356
-
2357
- function useFrame(callback, priorityOrOptions) {
2358
- const store = React__namespace.useContext(context);
2359
- const isInsideCanvas = store !== null;
2360
- const scheduler = getScheduler();
2361
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2362
- id: priorityOrOptions.id,
2363
- phase: priorityOrOptions.phase,
2364
- priority: priorityOrOptions.priority,
2365
- fps: priorityOrOptions.fps,
2366
- drop: priorityOrOptions.drop,
2367
- enabled: priorityOrOptions.enabled,
2368
- before: priorityOrOptions.before,
2369
- after: priorityOrOptions.after
2370
- }) : "";
2371
- const options = React__namespace.useMemo(() => {
2372
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
2373
- }, [optionsKey]);
2374
- const reactId = React__namespace.useId();
2375
- const id = options.id ?? reactId;
2376
- const callbackRef = useMutableCallback(callback);
2377
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2378
- useIsomorphicLayoutEffect(() => {
2379
- if (!callback) return;
2380
- if (isInsideCanvas) {
2381
- const state = store.getState();
2382
- const rootId = state.internal.rootId;
2383
- if (isLegacyPriority) {
2384
- state.internal.priority++;
2385
- let parentRoot = state.previousRoot;
2386
- while (parentRoot) {
2387
- const parentState = parentRoot.getState();
2388
- if (parentState?.internal) parentState.internal.priority++;
2389
- parentRoot = parentState?.previousRoot;
2390
- }
2391
- notifyDepreciated({
2392
- heading: "useFrame with numeric priority is deprecated",
2393
- 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 })',
2394
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2395
- });
1991
+ function useFrame(callback, priorityOrOptions) {
1992
+ const store = React__namespace.useContext(context);
1993
+ const isInsideCanvas = store !== null;
1994
+ const scheduler$1 = scheduler.getScheduler();
1995
+ const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1996
+ id: priorityOrOptions.id,
1997
+ phase: priorityOrOptions.phase,
1998
+ priority: priorityOrOptions.priority,
1999
+ fps: priorityOrOptions.fps,
2000
+ drop: priorityOrOptions.drop,
2001
+ enabled: priorityOrOptions.enabled,
2002
+ before: priorityOrOptions.before,
2003
+ after: priorityOrOptions.after
2004
+ }) : "";
2005
+ const options = React__namespace.useMemo(() => {
2006
+ return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
2007
+ }, [optionsKey]);
2008
+ const reactId = React__namespace.useId();
2009
+ const id = options.id ?? reactId;
2010
+ const callbackRef = useMutableCallback(callback);
2011
+ const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2012
+ useIsomorphicLayoutEffect(() => {
2013
+ if (!callback) return;
2014
+ if (isInsideCanvas) {
2015
+ const state = store.getState();
2016
+ const rootId = state.internal.rootId;
2017
+ if (isLegacyPriority) {
2018
+ state.internal.priority++;
2019
+ let parentRoot = state.previousRoot;
2020
+ while (parentRoot) {
2021
+ const parentState = parentRoot.getState();
2022
+ if (parentState?.internal) parentState.internal.priority++;
2023
+ parentRoot = parentState?.previousRoot;
2024
+ }
2025
+ notifyDepreciated({
2026
+ heading: "useFrame with numeric priority is deprecated",
2027
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
2028
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2029
+ });
2396
2030
  }
2397
2031
  const wrappedCallback = (frameState, delta) => {
2398
2032
  const localState = store.getState();
@@ -2405,7 +2039,7 @@ function useFrame(callback, priorityOrOptions) {
2405
2039
  };
2406
2040
  callbackRef.current?.(mergedState, delta);
2407
2041
  };
2408
- const unregister = scheduler.register(wrappedCallback, {
2042
+ const unregister = scheduler$1.register(wrappedCallback, {
2409
2043
  id,
2410
2044
  rootId,
2411
2045
  ...options
@@ -2426,37 +2060,31 @@ function useFrame(callback, priorityOrOptions) {
2426
2060
  }
2427
2061
  };
2428
2062
  } else {
2429
- const registerOutside = () => {
2430
- return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2431
- };
2432
- if (scheduler.independent || scheduler.isReady) {
2433
- return registerOutside();
2434
- }
2435
- let unregisterJob = null;
2436
- const unsubReady = scheduler.onRootReady(() => {
2437
- unregisterJob = registerOutside();
2438
- });
2439
- return () => {
2440
- unsubReady();
2441
- unregisterJob?.();
2442
- };
2063
+ return scheduler$1.register(
2064
+ (state, delta) => {
2065
+ const frameState = state;
2066
+ if (!frameState.renderer) return;
2067
+ callbackRef.current?.(frameState, delta);
2068
+ },
2069
+ { id, ...options }
2070
+ );
2443
2071
  }
2444
- }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2072
+ }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2445
2073
  const isPaused = React__namespace.useSyncExternalStore(
2446
2074
  // Subscribe function
2447
2075
  React__namespace.useCallback(
2448
2076
  (onStoreChange) => {
2449
- return getScheduler().subscribeJobState(id, onStoreChange);
2077
+ return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
2450
2078
  },
2451
2079
  [id]
2452
2080
  ),
2453
2081
  // getSnapshot function
2454
- React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
2082
+ React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
2455
2083
  // getServerSnapshot function (SSR)
2456
2084
  React__namespace.useCallback(() => false, [])
2457
2085
  );
2458
2086
  const controls = React__namespace.useMemo(() => {
2459
- const scheduler2 = getScheduler();
2087
+ const scheduler2 = scheduler.getScheduler();
2460
2088
  return {
2461
2089
  /** The job's unique ID */
2462
2090
  id,
@@ -2471,7 +2099,7 @@ function useFrame(callback, priorityOrOptions) {
2471
2099
  * @param timestamp Optional timestamp (defaults to performance.now())
2472
2100
  */
2473
2101
  step: (timestamp) => {
2474
- getScheduler().stepJob(id, timestamp);
2102
+ scheduler.getScheduler().stepJob(id, timestamp);
2475
2103
  },
2476
2104
  /**
2477
2105
  * Manually step ALL jobs in the scheduler.
@@ -2479,20 +2107,20 @@ function useFrame(callback, priorityOrOptions) {
2479
2107
  * @param timestamp Optional timestamp (defaults to performance.now())
2480
2108
  */
2481
2109
  stepAll: (timestamp) => {
2482
- getScheduler().step(timestamp);
2110
+ scheduler.getScheduler().step(timestamp);
2483
2111
  },
2484
2112
  /**
2485
2113
  * Pause this job (set enabled=false).
2486
2114
  * Job remains registered but won't run.
2487
2115
  */
2488
2116
  pause: () => {
2489
- getScheduler().pauseJob(id);
2117
+ scheduler.getScheduler().pauseJob(id);
2490
2118
  },
2491
2119
  /**
2492
2120
  * Resume this job (set enabled=true).
2493
2121
  */
2494
2122
  resume: () => {
2495
- getScheduler().resumeJob(id);
2123
+ scheduler.getScheduler().resumeJob(id);
2496
2124
  },
2497
2125
  /**
2498
2126
  * Reactive paused state - automatically updates when pause/resume is called.
@@ -2530,22 +2158,29 @@ function buildFromCache(input, textureCache) {
2530
2158
  function useTexture(input, optionsOrOnLoad) {
2531
2159
  const renderer = useThree((state) => state.internal.actualRenderer);
2532
2160
  const store = useStore();
2533
- const textureCache = useThree((state) => state.textures);
2534
2161
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2535
- const { onLoad, cache = false } = options;
2162
+ const { onLoad, cache = true } = options;
2163
+ const onLoadRef = React.useRef(onLoad);
2164
+ onLoadRef.current = onLoad;
2165
+ const onLoadCalledForRef = React.useRef(null);
2536
2166
  const urls = React.useMemo(() => getUrls(input), [input]);
2537
2167
  const cachedResult = React.useMemo(() => {
2538
2168
  if (!cache) return null;
2539
- if (!allUrlsCached(urls, textureCache)) return null;
2540
- return buildFromCache(input, textureCache);
2541
- }, [cache, urls, textureCache, input]);
2169
+ const textures = store.getState().textures;
2170
+ if (!allUrlsCached(urls, textures)) return null;
2171
+ return buildFromCache(input, textures);
2172
+ }, [cache, urls, input, store]);
2542
2173
  const loadedTextures = useLoader(
2543
2174
  webgpu.TextureLoader,
2544
2175
  IsObject(input) ? Object.values(input) : input
2545
2176
  );
2177
+ const inputKey = urls.join("\0");
2546
2178
  React.useLayoutEffect(() => {
2547
- if (!cachedResult) onLoad?.(loadedTextures);
2548
- }, [onLoad, cachedResult, loadedTextures]);
2179
+ if (cachedResult) return;
2180
+ if (onLoadCalledForRef.current === inputKey) return;
2181
+ onLoadCalledForRef.current = inputKey;
2182
+ onLoadRef.current?.(loadedTextures);
2183
+ }, [cachedResult, loadedTextures, inputKey]);
2549
2184
  React.useEffect(() => {
2550
2185
  if (cachedResult) return;
2551
2186
  if ("initTexture" in renderer) {
@@ -2578,8 +2213,6 @@ function useTexture(input, optionsOrOnLoad) {
2578
2213
  }, [input, loadedTextures, cachedResult]);
2579
2214
  React.useEffect(() => {
2580
2215
  if (!cache) return;
2581
- if (cachedResult) return;
2582
- const set = store.setState;
2583
2216
  const urlTextureMap = [];
2584
2217
  if (typeof input === "string") {
2585
2218
  urlTextureMap.push([input, mappedTextures]);
@@ -2593,18 +2226,32 @@ function useTexture(input, optionsOrOnLoad) {
2593
2226
  urlTextureMap.push([url, textureRecord[key]]);
2594
2227
  }
2595
2228
  }
2596
- set((state) => {
2597
- const newMap = new Map(state.textures);
2598
- let changed = false;
2229
+ store.setState((state) => {
2230
+ const refs = new Map(state._textureRefs);
2231
+ let textures = state.textures;
2232
+ let added = false;
2599
2233
  for (const [url, texture] of urlTextureMap) {
2600
- if (!newMap.has(url)) {
2601
- newMap.set(url, texture);
2602
- changed = true;
2234
+ if (!textures.has(url)) {
2235
+ if (!added) {
2236
+ textures = new Map(textures);
2237
+ added = true;
2238
+ }
2239
+ textures.set(url, texture);
2603
2240
  }
2241
+ refs.set(url, (refs.get(url) ?? 0) + 1);
2604
2242
  }
2605
- return changed ? { textures: newMap } : state;
2243
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
2244
+ });
2245
+ return () => store.setState((state) => {
2246
+ const refs = new Map(state._textureRefs);
2247
+ for (const [url] of urlTextureMap) {
2248
+ const next = (refs.get(url) ?? 0) - 1;
2249
+ if (next <= 0) refs.delete(url);
2250
+ else refs.set(url, next);
2251
+ }
2252
+ return { _textureRefs: refs };
2606
2253
  });
2607
- }, [cache, input, mappedTextures, store, cachedResult]);
2254
+ }, [cache, input, mappedTextures, store]);
2608
2255
  return mappedTextures;
2609
2256
  }
2610
2257
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -2620,106 +2267,90 @@ const Texture = ({
2620
2267
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
2621
2268
  };
2622
2269
 
2623
- function getTextureValue(entry) {
2624
- if (entry instanceof webgpu.Texture) return entry;
2625
- if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
2626
- return entry.value;
2627
- }
2628
- return null;
2629
- }
2630
- function useTextures() {
2270
+ function useTextures(selector) {
2631
2271
  const store = useStore();
2632
- return React.useMemo(() => {
2633
- const set = store.setState;
2272
+ const registry = React.useMemo(() => {
2634
2273
  const getState = store.getState;
2635
- const add = (key, value) => {
2636
- set((state) => {
2637
- const newMap = new Map(state.textures);
2638
- newMap.set(key, value);
2639
- return { textures: newMap };
2640
- });
2641
- };
2642
- const addMultiple = (items) => {
2643
- set((state) => {
2644
- const newMap = new Map(state.textures);
2645
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
2646
- for (const [key, value] of entries) {
2647
- newMap.set(key, value);
2648
- }
2649
- return { textures: newMap };
2650
- });
2651
- };
2652
- const remove = (key) => {
2653
- set((state) => {
2654
- const newMap = new Map(state.textures);
2655
- newMap.delete(key);
2656
- return { textures: newMap };
2657
- });
2658
- };
2659
- const removeMultiple = (keys) => {
2660
- set((state) => {
2661
- const newMap = new Map(state.textures);
2662
- for (const key of keys) newMap.delete(key);
2663
- return { textures: newMap };
2664
- });
2665
- };
2666
- const dispose = (key) => {
2667
- const entry = getState().textures.get(key);
2668
- if (entry) {
2669
- const tex = getTextureValue(entry);
2670
- tex?.dispose();
2671
- }
2672
- remove(key);
2673
- };
2674
- const disposeMultiple = (keys) => {
2675
- const textures = getState().textures;
2676
- for (const key of keys) {
2677
- const entry = textures.get(key);
2678
- if (entry) {
2679
- const tex = getTextureValue(entry);
2680
- tex?.dispose();
2681
- }
2682
- }
2683
- removeMultiple(keys);
2684
- };
2685
- const disposeAll = () => {
2686
- const textures = getState().textures;
2687
- for (const entry of textures.values()) {
2688
- const tex = getTextureValue(entry);
2689
- tex?.dispose();
2690
- }
2691
- set({ textures: /* @__PURE__ */ new Map() });
2692
- };
2274
+ const setState = store.setState;
2275
+ const getOne = (key) => getState().textures.get(key);
2693
2276
  return {
2694
- // Getter for the textures Map (reactive via getState)
2695
- get textures() {
2277
+ get all() {
2696
2278
  return getState().textures;
2697
2279
  },
2698
- // Read
2699
- get: (key) => getState().textures.get(key),
2280
+ get(input) {
2281
+ if (typeof input === "string") return getOne(input);
2282
+ if (Array.isArray(input)) return input.map(getOne);
2283
+ const out = {};
2284
+ for (const name in input) out[name] = getOne(input[name]);
2285
+ return out;
2286
+ },
2700
2287
  has: (key) => getState().textures.has(key),
2701
- // Write
2702
- add,
2703
- addMultiple,
2704
- // Remove (cache only)
2705
- remove,
2706
- removeMultiple,
2707
- // Dispose (GPU + cache)
2708
- dispose,
2709
- disposeMultiple,
2710
- disposeAll
2288
+ add(keyOrRecord, texture) {
2289
+ setState((state) => {
2290
+ const textures = new Map(state.textures);
2291
+ if (typeof keyOrRecord === "string") {
2292
+ textures.set(keyOrRecord, texture);
2293
+ } else {
2294
+ for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
2295
+ }
2296
+ return { textures };
2297
+ });
2298
+ },
2299
+ dispose(key, options) {
2300
+ const state = getState();
2301
+ const refs = state._textureRefs.get(key) ?? 0;
2302
+ if (refs > 0 && !options?.force) {
2303
+ console.warn(
2304
+ `[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
2305
+ );
2306
+ return false;
2307
+ }
2308
+ state.textures.get(key)?.dispose();
2309
+ setState((s) => {
2310
+ const textures = new Map(s.textures);
2311
+ textures.delete(key);
2312
+ const nextRefs = new Map(s._textureRefs);
2313
+ nextRefs.delete(key);
2314
+ return { textures, _textureRefs: nextRefs };
2315
+ });
2316
+ return true;
2317
+ },
2318
+ disposeAll() {
2319
+ for (const texture of getState().textures.values()) texture.dispose();
2320
+ setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
2321
+ }
2711
2322
  };
2712
2323
  }, [store]);
2324
+ const subscribe = selector ? () => selector(registry) : (state) => state.textures;
2325
+ const selected = useThree(subscribe);
2326
+ return selector ? selected : registry;
2713
2327
  }
2714
2328
 
2715
- function useRenderTarget(width, height, options) {
2329
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2716
2330
  const isLegacy = useThree((s) => s.isLegacy);
2717
2331
  const size = useThree((s) => s.size);
2332
+ let width;
2333
+ let height;
2334
+ let opts;
2335
+ if (typeof widthOrOptions === "object") {
2336
+ opts = widthOrOptions;
2337
+ } else if (typeof widthOrOptions === "number") {
2338
+ width = widthOrOptions;
2339
+ if (typeof heightOrOptions === "object") {
2340
+ height = widthOrOptions;
2341
+ opts = heightOrOptions;
2342
+ } else if (typeof heightOrOptions === "number") {
2343
+ height = heightOrOptions;
2344
+ opts = options;
2345
+ } else {
2346
+ height = widthOrOptions;
2347
+ }
2348
+ }
2718
2349
  return React.useMemo(() => {
2719
2350
  const w = width ?? size.width;
2720
2351
  const h = height ?? size.height;
2721
- return new webgpu.RenderTarget(w, h, options);
2722
- }, [width, height, size.width, size.height, options, isLegacy]);
2352
+ return new webgpu.RenderTarget(w, h, opts);
2353
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2723
2354
  }
2724
2355
 
2725
2356
  function useStore() {
@@ -2747,7 +2378,7 @@ function addEffect(callback) {
2747
2378
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2748
2379
  });
2749
2380
  const id = `legacy_effect_${effectId++}`;
2750
- return getScheduler().registerGlobal("before", id, callback);
2381
+ return scheduler.getScheduler().registerGlobal("before", id, callback);
2751
2382
  }
2752
2383
  function addAfterEffect(callback) {
2753
2384
  notifyDepreciated({
@@ -2756,7 +2387,7 @@ function addAfterEffect(callback) {
2756
2387
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2757
2388
  });
2758
2389
  const id = `legacy_afterEffect_${effectId++}`;
2759
- return getScheduler().registerGlobal("after", id, callback);
2390
+ return scheduler.getScheduler().registerGlobal("after", id, callback);
2760
2391
  }
2761
2392
  function addTail(callback) {
2762
2393
  notifyDepreciated({
@@ -2764,33 +2395,23 @@ function addTail(callback) {
2764
2395
  body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
2765
2396
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2766
2397
  });
2767
- return getScheduler().onIdle(callback);
2398
+ return scheduler.getScheduler().onIdle(callback);
2768
2399
  }
2769
2400
  function invalidate(state, frames = 1, stackFrames = false) {
2770
- getScheduler().invalidate(frames, stackFrames);
2401
+ scheduler.getScheduler().invalidate(frames, stackFrames);
2771
2402
  }
2772
- function advance(timestamp, runGlobalEffects = true, state, frame) {
2773
- getScheduler().step(timestamp);
2403
+ function advance(timestamp) {
2404
+ scheduler.getScheduler().step(timestamp);
2774
2405
  }
2775
2406
 
2776
- const version = "10.0.0-alpha.1";
2407
+ const version = "10.0.0-alpha.2";
2777
2408
  const packageData = {
2778
2409
  version: version};
2779
2410
 
2780
2411
  function Xb(Tt) {
2781
2412
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
2782
2413
  }
2783
- var Rm = { exports: {} }, Og = { exports: {} };
2784
- /**
2785
- * @license React
2786
- * react-reconciler.production.js
2787
- *
2788
- * Copyright (c) Meta Platforms, Inc. and affiliates.
2789
- *
2790
- * This source code is licensed under the MIT license found in the
2791
- * LICENSE file in the root directory of this source tree.
2792
- */
2793
- var _b;
2414
+ var Rm = { exports: {} }, Og = { exports: {} }, _b;
2794
2415
  function Kb() {
2795
2416
  return _b || (_b = 1, (function(Tt) {
2796
2417
  Tt.exports = function(m) {
@@ -3862,7 +3483,6 @@ Error generating stack: ` + l.message + `
3862
3483
  if (J === cl || J === jc) throw J;
3863
3484
  var Ge = Yn(29, J, null, P.mode);
3864
3485
  return Ge.lanes = H, Ge.return = P, Ge;
3865
- } finally {
3866
3486
  }
3867
3487
  };
3868
3488
  }
@@ -4516,7 +4136,6 @@ Error generating stack: ` + l.message + `
4516
4136
  var h = r.lastRenderedState, y = d(h, a);
4517
4137
  if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
4518
4138
  } catch {
4519
- } finally {
4520
4139
  }
4521
4140
  if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
4522
4141
  }
@@ -6937,10 +6556,7 @@ Error generating stack: ` + l.message + `
6937
6556
  function vr(t, r) {
6938
6557
  Sf(t, r), (t = t.alternate) && Sf(t, r);
6939
6558
  }
6940
- var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
6941
- var gc = Symbol.for("react.activity");
6942
- var $r = Symbol.for("react.memo_cache_sentinel");
6943
- 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;
6559
+ var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
6944
6560
  m.cloneMutableInstance;
6945
6561
  var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
6946
6562
  m.cloneMutableTextInstance;
@@ -7309,17 +6925,7 @@ No matching component was found for:
7309
6925
  }, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
7310
6926
  })(Og)), Og.exports;
7311
6927
  }
7312
- var Mg = { exports: {} };
7313
- /**
7314
- * @license React
7315
- * react-reconciler.development.js
7316
- *
7317
- * Copyright (c) Meta Platforms, Inc. and affiliates.
7318
- *
7319
- * This source code is licensed under the MIT license found in the
7320
- * LICENSE file in the root directory of this source tree.
7321
- */
7322
- var Rb;
6928
+ var Mg = { exports: {} }, Rb;
7323
6929
  function e0() {
7324
6930
  return Rb || (Rb = 1, (function(Tt) {
7325
6931
  process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
@@ -13086,10 +12692,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
13086
12692
  function Ic() {
13087
12693
  return di;
13088
12694
  }
13089
- var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
13090
- var Ds = Symbol.for("react.activity");
13091
- var Bh = Symbol.for("react.memo_cache_sentinel");
13092
- 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;
12695
+ var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
13093
12696
  m.cloneMutableInstance;
13094
12697
  var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
13095
12698
  m.cloneMutableTextInstance;
@@ -14057,15 +13660,6 @@ function n0() {
14057
13660
  var t0 = n0();
14058
13661
  const r0 = Xb(t0);
14059
13662
 
14060
- /**
14061
- * @license React
14062
- * react-reconciler-constants.production.js
14063
- *
14064
- * Copyright (c) Meta Platforms, Inc. and affiliates.
14065
- *
14066
- * This source code is licensed under the MIT license found in the
14067
- * LICENSE file in the root directory of this source tree.
14068
- */
14069
13663
  const t = 1, o = 8, r = 32, e = 2;
14070
13664
 
14071
13665
  function createReconciler(config) {
@@ -14092,10 +13686,11 @@ function extend(objects) {
14092
13686
  function validateInstance(type, props) {
14093
13687
  const name = toPascalCase(type);
14094
13688
  const target = catalogue[name];
14095
- if (type !== "primitive" && !target)
13689
+ if (type !== "primitive" && !target) {
14096
13690
  throw new Error(
14097
13691
  `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`
14098
13692
  );
13693
+ }
14099
13694
  if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
14100
13695
  if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
14101
13696
  }
@@ -14259,6 +13854,7 @@ function swapInstances() {
14259
13854
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14260
13855
  instance.object.__r3f = instance;
14261
13856
  setFiberRef(fiber, instance.object);
13857
+ delete instance.appliedOnce;
14262
13858
  applyProps(instance.object, instance.props);
14263
13859
  if (instance.props.attach) {
14264
13860
  attach(parent, instance);
@@ -14332,8 +13928,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14332
13928
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14333
13929
  if (isTailSibling) swapInstances();
14334
13930
  },
14335
- finalizeInitialChildren: () => false,
14336
- commitMount() {
13931
+ finalizeInitialChildren: (instance) => {
13932
+ for (const prop in instance.props) {
13933
+ if (isFromRef(instance.props[prop])) return true;
13934
+ }
13935
+ return false;
13936
+ },
13937
+ commitMount(instance) {
13938
+ const resolved = {};
13939
+ for (const prop in instance.props) {
13940
+ const value = instance.props[prop];
13941
+ if (isFromRef(value)) {
13942
+ const ref = value[FROM_REF];
13943
+ if (ref.current != null) resolved[prop] = ref.current;
13944
+ }
13945
+ }
13946
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14337
13947
  },
14338
13948
  getPublicInstance: (instance) => instance?.object,
14339
13949
  prepareForCommit: () => null,
@@ -14546,14 +14156,17 @@ function createRoot(canvas) {
14546
14156
  if (!prevRoot) _roots.set(canvas, { fiber, store });
14547
14157
  let onCreated;
14548
14158
  let lastCamera;
14549
- let lastConfiguredProps = {};
14159
+ const lastConfiguredProps = {};
14550
14160
  let configured = false;
14551
14161
  let pending = null;
14552
14162
  return {
14553
14163
  async configure(props = {}) {
14554
14164
  let resolve;
14555
14165
  pending = new Promise((_resolve) => resolve = _resolve);
14556
- let {
14166
+ const {
14167
+ id: canvasId,
14168
+ primaryCanvas,
14169
+ scheduler: schedulerConfig,
14557
14170
  gl: glConfig,
14558
14171
  renderer: rendererConfig,
14559
14172
  size: propsSize,
@@ -14561,10 +14174,6 @@ function createRoot(canvas) {
14561
14174
  events,
14562
14175
  onCreated: onCreatedCallback,
14563
14176
  shadows = false,
14564
- linear = false,
14565
- flat = false,
14566
- textureColorSpace = webgpu.SRGBColorSpace,
14567
- legacy = false,
14568
14177
  orthographic = false,
14569
14178
  frameloop = "always",
14570
14179
  dpr = [1, 2],
@@ -14575,11 +14184,15 @@ function createRoot(canvas) {
14575
14184
  onDragOverMissed,
14576
14185
  onDropMissed,
14577
14186
  autoUpdateFrustum = true,
14578
- occlusion = false
14187
+ occlusion = false,
14188
+ _sizeProps,
14189
+ forceEven
14579
14190
  } = props;
14580
- let state = store.getState();
14191
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
14192
+ const state = store.getState();
14581
14193
  const defaultGPUProps = {
14582
- canvas
14194
+ canvas,
14195
+ antialias: true
14583
14196
  };
14584
14197
  if (glConfig && !R3F_BUILD_LEGACY) {
14585
14198
  throw new Error(
@@ -14590,22 +14203,61 @@ function createRoot(canvas) {
14590
14203
  throw new Error("Cannot use both gl and renderer props at the same time");
14591
14204
  }
14592
14205
  let renderer = state.internal.actualRenderer;
14593
- if (!state.internal.actualRenderer) {
14206
+ if (primaryCanvas && !state.internal.actualRenderer) {
14207
+ const primary = await waitForPrimary(primaryCanvas);
14208
+ renderer = primary.renderer;
14209
+ state.internal.actualRenderer = renderer;
14210
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14211
+ primary.store.setState((prev) => ({
14212
+ internal: { ...prev.internal, isMultiCanvas: true }
14213
+ }));
14214
+ state.set((prev) => ({
14215
+ webGPUSupported: primary.store.getState().webGPUSupported,
14216
+ renderer,
14217
+ primaryStore: primary.store,
14218
+ internal: {
14219
+ ...prev.internal,
14220
+ canvasTarget,
14221
+ isMultiCanvas: true,
14222
+ isSecondary: true,
14223
+ targetId: primaryCanvas
14224
+ }
14225
+ }));
14226
+ } else if (!state.internal.actualRenderer) {
14594
14227
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14595
14228
  if (!renderer.hasInitialized?.()) {
14229
+ const size2 = computeInitialSize(canvas, propsSize);
14230
+ if (size2.width > 0 && size2.height > 0) {
14231
+ const pixelRatio = calculateDpr(dpr);
14232
+ canvas.width = size2.width * pixelRatio;
14233
+ canvas.height = size2.height * pixelRatio;
14234
+ }
14596
14235
  await renderer.init();
14597
14236
  }
14598
14237
  const backend = renderer.backend;
14599
14238
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14600
14239
  state.internal.actualRenderer = renderer;
14601
- state.set({ webGPUSupported: isWebGPUBackend, renderer });
14240
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14241
+ if (canvasId && !state.internal.isSecondary) {
14242
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14243
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14244
+ state.set((prev) => ({
14245
+ internal: {
14246
+ ...prev.internal,
14247
+ canvasTarget,
14248
+ unregisterPrimary
14249
+ }
14250
+ }));
14251
+ }
14602
14252
  }
14603
14253
  let raycaster = state.raycaster;
14604
14254
  if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
14605
14255
  const { params, ...options } = raycastOptions || {};
14606
14256
  if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
14607
- if (!is.equ(params, raycaster.params, shallowLoose))
14257
+ if (!is.equ(params, raycaster.params, shallowLoose)) {
14608
14258
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14259
+ }
14260
+ let tempCamera = state.camera;
14609
14261
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14610
14262
  lastCamera = cameraOptions;
14611
14263
  const isCamera = cameraOptions?.isCamera;
@@ -14625,6 +14277,7 @@ function createRoot(canvas) {
14625
14277
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14626
14278
  }
14627
14279
  state.set({ camera });
14280
+ tempCamera = camera;
14628
14281
  raycaster.camera = camera;
14629
14282
  }
14630
14283
  if (!state.scene) {
@@ -14642,7 +14295,7 @@ function createRoot(canvas) {
14642
14295
  rootScene: scene,
14643
14296
  internal: { ...prev.internal, container: scene }
14644
14297
  }));
14645
- const camera = state.camera;
14298
+ const camera = tempCamera;
14646
14299
  if (camera && !camera.parent) scene.add(camera);
14647
14300
  }
14648
14301
  if (events && !state.events.handlers) {
@@ -14656,9 +14309,17 @@ function createRoot(canvas) {
14656
14309
  wasEnabled = enabled;
14657
14310
  });
14658
14311
  }
14312
+ if (_sizeProps !== void 0) {
14313
+ state.set({ _sizeProps });
14314
+ }
14315
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
14316
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
14317
+ }
14659
14318
  const size = computeInitialSize(canvas, propsSize);
14660
- if (!is.equ(size, state.size, shallowLoose)) {
14319
+ if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14320
+ const wasImperative = state._sizeImperative;
14661
14321
  state.setSize(size.width, size.height, size.top, size.left);
14322
+ if (!wasImperative) state.set({ _sizeImperative: false });
14662
14323
  }
14663
14324
  if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
14664
14325
  state.setDpr(dpr);
@@ -14680,10 +14341,10 @@ function createRoot(canvas) {
14680
14341
  lastConfiguredProps.performance = performance;
14681
14342
  }
14682
14343
  if (!state.xr) {
14683
- const handleXRFrame = (timestamp, frame) => {
14344
+ const handleXRFrame = (timestamp, _frame) => {
14684
14345
  const state2 = store.getState();
14685
14346
  if (state2.frameloop === "never") return;
14686
- advance(timestamp, true);
14347
+ advance(timestamp);
14687
14348
  };
14688
14349
  const actualRenderer = state.internal.actualRenderer;
14689
14350
  const handleSessionChange = () => {
@@ -14695,16 +14356,16 @@ function createRoot(canvas) {
14695
14356
  };
14696
14357
  const xr = {
14697
14358
  connect() {
14698
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14699
- const actualRenderer2 = renderer2 || gl;
14700
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14701
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
14359
+ const { gl, renderer: renderer2 } = store.getState();
14360
+ const xrManager = (renderer2 || gl).xr;
14361
+ xrManager.addEventListener("sessionstart", handleSessionChange);
14362
+ xrManager.addEventListener("sessionend", handleSessionChange);
14702
14363
  },
14703
14364
  disconnect() {
14704
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14705
- const actualRenderer2 = renderer2 || gl;
14706
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14707
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
14365
+ const { gl, renderer: renderer2 } = store.getState();
14366
+ const xrManager = (renderer2 || gl).xr;
14367
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
14368
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14708
14369
  }
14709
14370
  };
14710
14371
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14716,42 +14377,93 @@ function createRoot(canvas) {
14716
14377
  const oldType = renderer.shadowMap.type;
14717
14378
  renderer.shadowMap.enabled = !!shadows;
14718
14379
  if (is.boo(shadows)) {
14719
- renderer.shadowMap.type = webgpu.PCFSoftShadowMap;
14380
+ renderer.shadowMap.type = webgpu.PCFShadowMap;
14720
14381
  } else if (is.str(shadows)) {
14382
+ if (shadows === "soft") {
14383
+ notifyDepreciated({
14384
+ heading: 'shadows="soft" is deprecated',
14385
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
14386
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
14387
+ });
14388
+ }
14721
14389
  const types = {
14722
14390
  basic: webgpu.BasicShadowMap,
14723
14391
  percentage: webgpu.PCFShadowMap,
14724
- soft: webgpu.PCFSoftShadowMap,
14392
+ soft: webgpu.PCFShadowMap,
14725
14393
  variance: webgpu.VSMShadowMap
14726
14394
  };
14727
- renderer.shadowMap.type = types[shadows] ?? webgpu.PCFSoftShadowMap;
14395
+ renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
14728
14396
  } else if (is.obj(shadows)) {
14729
14397
  Object.assign(renderer.shadowMap, shadows);
14730
14398
  }
14731
- if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
14399
+ if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
14732
14400
  renderer.shadowMap.needsUpdate = true;
14401
+ }
14402
+ }
14403
+ if (!configured) {
14404
+ renderer.outputColorSpace = webgpu.SRGBColorSpace;
14405
+ renderer.toneMapping = webgpu.ACESFilmicToneMapping;
14733
14406
  }
14734
14407
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14735
14408
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14736
14409
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14737
14410
  }
14738
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose))
14739
- applyProps(renderer, glConfig);
14411
+ const r3fProps = ["textureColorSpace"];
14412
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
14413
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14414
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14415
+ const glProps = {};
14416
+ for (const key in glConfig) {
14417
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
14418
+ }
14419
+ applyProps(renderer, glProps);
14420
+ }
14740
14421
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14741
14422
  const currentRenderer = state.renderer;
14742
14423
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14743
- applyProps(currentRenderer, rendererConfig);
14424
+ const rendererProps = {};
14425
+ for (const key in rendererConfig) {
14426
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
14427
+ }
14428
+ applyProps(currentRenderer, rendererProps);
14744
14429
  }
14745
14430
  }
14746
- const scheduler = getScheduler();
14431
+ const scheduler$1 = scheduler.getScheduler();
14747
14432
  const rootId = state.internal.rootId;
14748
14433
  if (!rootId) {
14749
- const newRootId = scheduler.generateRootId();
14750
- const unregisterRoot = scheduler.registerRoot(newRootId, {
14434
+ const newRootId = canvasId || scheduler$1.generateRootId();
14435
+ const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14751
14436
  getState: () => store.getState(),
14752
14437
  onError: (err) => store.getState().setError(err)
14753
14438
  });
14754
- const unregisterFrustum = scheduler.register(
14439
+ const unregisterCanvasTarget = scheduler$1.register(
14440
+ () => {
14441
+ const state2 = store.getState();
14442
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14443
+ const renderer2 = state2.internal.actualRenderer;
14444
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
14445
+ }
14446
+ },
14447
+ {
14448
+ id: `${newRootId}_canvasTarget`,
14449
+ rootId: newRootId,
14450
+ phase: "start",
14451
+ system: true
14452
+ }
14453
+ );
14454
+ const unregisterEventsFlush = scheduler$1.register(
14455
+ () => {
14456
+ const state2 = store.getState();
14457
+ state2.events.flush?.();
14458
+ },
14459
+ {
14460
+ id: `${newRootId}_events`,
14461
+ rootId: newRootId,
14462
+ phase: "input",
14463
+ system: true
14464
+ }
14465
+ );
14466
+ const unregisterFrustum = scheduler$1.register(
14755
14467
  () => {
14756
14468
  const state2 = store.getState();
14757
14469
  if (state2.autoUpdateFrustum && state2.camera) {
@@ -14761,11 +14473,11 @@ function createRoot(canvas) {
14761
14473
  {
14762
14474
  id: `${newRootId}_frustum`,
14763
14475
  rootId: newRootId,
14764
- phase: "preRender",
14476
+ before: "render",
14765
14477
  system: true
14766
14478
  }
14767
14479
  );
14768
- const unregisterVisibility = scheduler.register(
14480
+ const unregisterVisibility = scheduler$1.register(
14769
14481
  () => {
14770
14482
  const state2 = store.getState();
14771
14483
  checkVisibility(state2);
@@ -14773,30 +14485,34 @@ function createRoot(canvas) {
14773
14485
  {
14774
14486
  id: `${newRootId}_visibility`,
14775
14487
  rootId: newRootId,
14776
- phase: "preRender",
14488
+ before: "render",
14777
14489
  system: true,
14778
14490
  after: `${newRootId}_frustum`
14779
14491
  }
14780
14492
  );
14781
- const unregisterRender = scheduler.register(
14493
+ const unregisterRender = scheduler$1.register(
14782
14494
  () => {
14783
14495
  const state2 = store.getState();
14784
14496
  const renderer2 = state2.internal.actualRenderer;
14785
- const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14497
+ const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14786
14498
  if (userHandlesRender || state2.internal.priority) return;
14787
14499
  try {
14788
- if (state2.postProcessing?.render) state2.postProcessing.render();
14500
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14789
14501
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14790
14502
  } catch (error) {
14791
14503
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14792
14504
  }
14793
14505
  },
14794
14506
  {
14795
- id: `${newRootId}_render`,
14507
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
14508
+ id: canvasId || `${newRootId}_render`,
14796
14509
  rootId: newRootId,
14797
14510
  phase: "render",
14798
- system: true
14511
+ system: true,
14799
14512
  // Internal flag: this is a system job, not user-controlled
14513
+ // Apply scheduler config for render ordering and rate limiting
14514
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
14515
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14800
14516
  }
14801
14517
  );
14802
14518
  state.set((state2) => ({
@@ -14805,15 +14521,17 @@ function createRoot(canvas) {
14805
14521
  rootId: newRootId,
14806
14522
  unregisterRoot: () => {
14807
14523
  unregisterRoot();
14524
+ unregisterCanvasTarget();
14525
+ unregisterEventsFlush();
14808
14526
  unregisterFrustum();
14809
14527
  unregisterVisibility();
14810
14528
  unregisterRender();
14811
14529
  },
14812
- scheduler
14530
+ scheduler: scheduler$1
14813
14531
  }
14814
14532
  }));
14815
14533
  }
14816
- scheduler.frameloop = frameloop;
14534
+ scheduler$1.frameloop = frameloop;
14817
14535
  onCreated = onCreatedCallback;
14818
14536
  configured = true;
14819
14537
  resolve();
@@ -14863,15 +14581,24 @@ function unmountComponentAtNode(canvas, callback) {
14863
14581
  const renderer = state.internal.actualRenderer;
14864
14582
  const unregisterRoot = state.internal.unregisterRoot;
14865
14583
  if (unregisterRoot) unregisterRoot();
14584
+ const unregisterPrimary = state.internal.unregisterPrimary;
14585
+ if (unregisterPrimary) unregisterPrimary();
14586
+ const canvasTarget = state.internal.canvasTarget;
14587
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14866
14588
  state.events.disconnect?.();
14867
14589
  cleanupHelperGroup(root.store);
14868
- renderer?.renderLists?.dispose?.();
14869
- renderer?.forceContextLoss?.();
14870
- if (renderer?.xr) state.xr.disconnect();
14590
+ if (state.isLegacy && renderer) {
14591
+ ;
14592
+ renderer.renderLists?.dispose?.();
14593
+ renderer.forceContextLoss?.();
14594
+ }
14595
+ if (!state.internal.isSecondary) {
14596
+ if (renderer?.xr) state.xr.disconnect();
14597
+ }
14871
14598
  dispose(state.scene);
14872
14599
  _roots.delete(canvas);
14873
14600
  if (callback) callback(canvas);
14874
- } catch (e) {
14601
+ } catch {
14875
14602
  }
14876
14603
  }, 500);
14877
14604
  }
@@ -14879,36 +14606,34 @@ function unmountComponentAtNode(canvas, callback) {
14879
14606
  }
14880
14607
  }
14881
14608
  function createPortal(children, container, state) {
14882
- return /* @__PURE__ */ jsxRuntime.jsx(PortalWrapper, { children, container, state });
14609
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
14883
14610
  }
14884
- function PortalWrapper({ children, container, state }) {
14611
+ function Portal({ children, container, state }) {
14885
14612
  const isRef = React.useCallback((obj) => obj && "current" in obj, []);
14886
- const [resolvedContainer, setResolvedContainer] = React.useState(() => {
14613
+ const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
14887
14614
  if (isRef(container)) return container.current ?? null;
14888
14615
  return container;
14889
14616
  });
14617
+ const setResolvedContainer = React.useCallback(
14618
+ (newContainer) => {
14619
+ if (!newContainer || newContainer === resolvedContainer) return;
14620
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
14621
+ },
14622
+ [resolvedContainer, _setResolvedContainer, isRef]
14623
+ );
14890
14624
  React.useMemo(() => {
14891
- if (isRef(container)) {
14892
- const current = container.current;
14893
- if (!current) {
14894
- queueMicrotask(() => {
14895
- const updated = container.current;
14896
- if (updated && updated !== resolvedContainer) {
14897
- setResolvedContainer(updated);
14898
- }
14899
- });
14900
- } else if (current !== resolvedContainer) {
14901
- setResolvedContainer(current);
14902
- }
14903
- } else if (container !== resolvedContainer) {
14904
- setResolvedContainer(container);
14625
+ if (isRef(container) && !container.current) {
14626
+ return queueMicrotask(() => {
14627
+ setResolvedContainer(container.current);
14628
+ });
14905
14629
  }
14906
- }, [container, resolvedContainer, isRef]);
14630
+ setResolvedContainer(container);
14631
+ }, [container, isRef, setResolvedContainer]);
14907
14632
  if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
14908
14633
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14909
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
14634
+ return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14910
14635
  }
14911
- function Portal({ state = {}, children, container }) {
14636
+ function PortalInner({ state = {}, children, container }) {
14912
14637
  const { events, size, injectScene = true, ...rest } = state;
14913
14638
  const previousRoot = useStore();
14914
14639
  const [raycaster] = React.useState(() => new webgpu.Raycaster());
@@ -14929,11 +14654,12 @@ function Portal({ state = {}, children, container }) {
14929
14654
  };
14930
14655
  }, [portalScene, container, injectScene]);
14931
14656
  const inject = useMutableCallback((rootState, injectState) => {
14657
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14932
14658
  let viewport = void 0;
14933
- if (injectState.camera && size) {
14659
+ if (injectState.camera && (size || injectState.size)) {
14934
14660
  const camera = injectState.camera;
14935
- viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), size);
14936
- if (camera !== rootState.camera) updateCamera(camera, size);
14661
+ viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
14662
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14937
14663
  }
14938
14664
  return {
14939
14665
  // The intersect consists of the previous root state
@@ -14950,7 +14676,7 @@ function Portal({ state = {}, children, container }) {
14950
14676
  previousRoot,
14951
14677
  // Events, size and viewport can be overridden by the inject layer
14952
14678
  events: { ...rootState.events, ...injectState.events, ...events },
14953
- size: { ...rootState.size, ...size },
14679
+ size: resolvedSize,
14954
14680
  viewport: { ...rootState.viewport, ...viewport },
14955
14681
  // Layers are allowed to override events
14956
14682
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14962,9 +14688,13 @@ function Portal({ state = {}, children, container }) {
14962
14688
  const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
14963
14689
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
14964
14690
  onMutate(previousRoot.getState());
14965
- previousRoot.subscribe(onMutate);
14966
14691
  return store;
14967
14692
  }, [previousRoot, container]);
14693
+ useIsomorphicLayoutEffect(() => {
14694
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
14695
+ const unsubscribe = previousRoot.subscribe(onMutate);
14696
+ return unsubscribe;
14697
+ }, [previousRoot, usePortalStore]);
14968
14698
  return (
14969
14699
  // @ts-ignore, reconciler types are not maintained
14970
14700
  /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
@@ -14978,21 +14708,59 @@ function flushSync(fn) {
14978
14708
  return reconciler.flushSyncFromReconciler(fn);
14979
14709
  }
14980
14710
 
14711
+ function parseBackground(background) {
14712
+ if (!background) return null;
14713
+ if (typeof background === "object" && !background.isColor) {
14714
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
14715
+ return {
14716
+ ...rest,
14717
+ preset,
14718
+ files: envMap || files,
14719
+ backgroundFiles: backgroundMap,
14720
+ background: true
14721
+ };
14722
+ }
14723
+ if (typeof background === "number") {
14724
+ return { color: background, background: true };
14725
+ }
14726
+ if (typeof background === "string") {
14727
+ if (background in presetsObj) {
14728
+ return { preset: background, background: true };
14729
+ }
14730
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
14731
+ return { files: background, background: true };
14732
+ }
14733
+ return { color: background, background: true };
14734
+ }
14735
+ if (background.isColor) {
14736
+ return { color: background, background: true };
14737
+ }
14738
+ return null;
14739
+ }
14740
+
14741
+ function clearHmrCaches(store) {
14742
+ store.setState((state) => ({
14743
+ nodes: {},
14744
+ uniforms: {},
14745
+ buffers: {},
14746
+ gpuStorage: {},
14747
+ _hmrVersion: state._hmrVersion + 1
14748
+ }));
14749
+ }
14750
+
14981
14751
  function CanvasImpl({
14982
14752
  ref,
14983
14753
  children,
14984
14754
  fallback,
14985
14755
  resize,
14986
14756
  style,
14757
+ id,
14987
14758
  gl,
14988
- renderer,
14759
+ renderer: rendererProp,
14989
14760
  events = createPointerEvents,
14990
14761
  eventSource,
14991
14762
  eventPrefix,
14992
14763
  shadows,
14993
- linear,
14994
- flat,
14995
- legacy,
14996
14764
  orthographic,
14997
14765
  frameloop,
14998
14766
  dpr,
@@ -15000,14 +14768,34 @@ function CanvasImpl({
15000
14768
  raycaster,
15001
14769
  camera,
15002
14770
  scene,
14771
+ autoUpdateFrustum,
14772
+ occlusion,
15003
14773
  onPointerMissed,
15004
14774
  onDragOverMissed,
15005
14775
  onDropMissed,
15006
14776
  onCreated,
14777
+ hmr,
14778
+ width,
14779
+ height,
14780
+ background,
14781
+ forceEven,
15007
14782
  ...props
15008
14783
  }) {
14784
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14785
+ let primaryCanvas;
14786
+ let scheduler;
14787
+ let renderer;
14788
+ if (isRendererConfig) {
14789
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14790
+ primaryCanvas = pc;
14791
+ scheduler = sc;
14792
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14793
+ } else {
14794
+ renderer = rendererProp;
14795
+ }
15009
14796
  React__namespace.useMemo(() => extend(THREE), []);
15010
14797
  const Bridge = useBridge();
14798
+ const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15011
14799
  const hasInitialSizeRef = React__namespace.useRef(false);
15012
14800
  const measureConfig = React__namespace.useMemo(() => {
15013
14801
  if (!hasInitialSizeRef.current) {
@@ -15024,7 +14812,21 @@ function CanvasImpl({
15024
14812
  };
15025
14813
  }, [resize, hasInitialSizeRef.current]);
15026
14814
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15027
- if (!hasInitialSizeRef.current && containerRect.width > 0 && containerRect.height > 0) {
14815
+ const effectiveSize = React__namespace.useMemo(() => {
14816
+ let w = width ?? containerRect.width;
14817
+ let h = height ?? containerRect.height;
14818
+ if (forceEven) {
14819
+ w = Math.ceil(w / 2) * 2;
14820
+ h = Math.ceil(h / 2) * 2;
14821
+ }
14822
+ return {
14823
+ width: w,
14824
+ height: h,
14825
+ top: containerRect.top,
14826
+ left: containerRect.left
14827
+ };
14828
+ }, [width, height, containerRect, forceEven]);
14829
+ if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15028
14830
  hasInitialSizeRef.current = true;
15029
14831
  }
15030
14832
  const canvasRef = React__namespace.useRef(null);
@@ -15043,7 +14845,7 @@ function CanvasImpl({
15043
14845
  useIsomorphicLayoutEffect(() => {
15044
14846
  effectActiveRef.current = true;
15045
14847
  const canvas = canvasRef.current;
15046
- if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
14848
+ if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
15047
14849
  if (!root.current) {
15048
14850
  root.current = createRoot(canvas);
15049
14851
  notifyAlpha({
@@ -15063,21 +14865,26 @@ function CanvasImpl({
15063
14865
  async function run() {
15064
14866
  if (!effectActiveRef.current || !root.current) return;
15065
14867
  await root.current.configure({
14868
+ id,
14869
+ primaryCanvas,
14870
+ scheduler,
15066
14871
  gl,
15067
14872
  renderer,
15068
14873
  scene,
15069
14874
  events,
15070
14875
  shadows,
15071
- linear,
15072
- flat,
15073
- legacy,
15074
14876
  orthographic,
15075
14877
  frameloop,
15076
14878
  dpr,
15077
14879
  performance,
15078
14880
  raycaster,
15079
14881
  camera,
15080
- size: containerRect,
14882
+ autoUpdateFrustum,
14883
+ occlusion,
14884
+ size: effectiveSize,
14885
+ // Store size props for reset functionality
14886
+ _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
14887
+ forceEven,
15081
14888
  // Pass mutable reference to onPointerMissed so it's free to update
15082
14889
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15083
14890
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15101,7 +14908,10 @@ function CanvasImpl({
15101
14908
  });
15102
14909
  if (!effectActiveRef.current || !root.current) return;
15103
14910
  root.current.render(
15104
- /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: children ?? null }) }) })
14911
+ /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
14912
+ backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
14913
+ children ?? null
14914
+ ] }) }) })
15105
14915
  );
15106
14916
  }
15107
14917
  run();
@@ -15122,7 +14932,29 @@ function CanvasImpl({
15122
14932
  root.current = null;
15123
14933
  };
15124
14934
  }
15125
- }, []);
14935
+ }, []);
14936
+ React__namespace.useEffect(() => {
14937
+ if (hmr === false) return;
14938
+ const canvas = canvasRef.current;
14939
+ if (!canvas) return;
14940
+ const handleHMR = () => {
14941
+ queueMicrotask(() => {
14942
+ const rootEntry = _roots.get(canvas);
14943
+ if (rootEntry?.store) clearHmrCaches(rootEntry.store);
14944
+ });
14945
+ };
14946
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
14947
+ const hot = undefined;
14948
+ hot.on("vite:afterUpdate", handleHMR);
14949
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
14950
+ }
14951
+ if (typeof module !== "undefined" && module.hot) {
14952
+ const hot = module.hot;
14953
+ hot.addStatusHandler((status) => {
14954
+ if (status === "idle") handleHMR();
14955
+ });
14956
+ }
14957
+ }, [hmr]);
15126
14958
  const pointerEvents = eventSource ? "none" : "auto";
15127
14959
  return /* @__PURE__ */ jsxRuntime.jsx(
15128
14960
  "div",
@@ -15137,7 +14969,16 @@ function CanvasImpl({
15137
14969
  ...style
15138
14970
  },
15139
14971
  ...props,
15140
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
14972
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
14973
+ "canvas",
14974
+ {
14975
+ ref: canvasRef,
14976
+ id,
14977
+ className: "r3f-canvas",
14978
+ style: { display: "block", width: "100%", height: "100%" },
14979
+ children: fallback
14980
+ }
14981
+ ) })
15141
14982
  }
15142
14983
  );
15143
14984
  }
@@ -15145,6 +14986,114 @@ function Canvas(props) {
15145
14986
  return /* @__PURE__ */ jsxRuntime.jsx(itsFine.FiberProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(CanvasImpl, { ...props }) });
15146
14987
  }
15147
14988
 
14989
+ var __defProp = Object.defineProperty;
14990
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14991
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
14992
+ var _a;
14993
+ const INTERNAL_DATA = Symbol("ScopedStore.data");
14994
+ _a = INTERNAL_DATA;
14995
+ const _ScopedStore = class _ScopedStore {
14996
+ constructor(data) {
14997
+ __publicField(this, _a);
14998
+ this[INTERNAL_DATA] = data;
14999
+ return new Proxy(this, {
15000
+ get(target, prop, receiver) {
15001
+ if (typeof prop === "string") {
15002
+ if (prop === "scope" || prop === "has" || prop === "keys") {
15003
+ return Reflect.get(target, prop, receiver);
15004
+ }
15005
+ return target[INTERNAL_DATA][prop];
15006
+ }
15007
+ return Reflect.get(target, prop, receiver);
15008
+ },
15009
+ has(target, prop) {
15010
+ return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
15011
+ },
15012
+ ownKeys(target) {
15013
+ return Reflect.ownKeys(target[INTERNAL_DATA]);
15014
+ },
15015
+ getOwnPropertyDescriptor(target, prop) {
15016
+ if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
15017
+ return {
15018
+ configurable: true,
15019
+ enumerable: true,
15020
+ value: target[INTERNAL_DATA][prop]
15021
+ };
15022
+ }
15023
+ return void 0;
15024
+ }
15025
+ });
15026
+ }
15027
+ /**
15028
+ * Access a nested scope by key.
15029
+ * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15030
+ */
15031
+ scope(key) {
15032
+ const data = this[INTERNAL_DATA][key];
15033
+ return new _ScopedStore(
15034
+ data && typeof data === "object" ? data : {}
15035
+ );
15036
+ }
15037
+ /**
15038
+ * Check if a key exists in the store.
15039
+ */
15040
+ has(key) {
15041
+ return key in this[INTERNAL_DATA];
15042
+ }
15043
+ /**
15044
+ * Get all keys in the store.
15045
+ */
15046
+ keys() {
15047
+ return Object.keys(this[INTERNAL_DATA]);
15048
+ }
15049
+ };
15050
+ let ScopedStore = _ScopedStore;
15051
+ function createScopedStore(data) {
15052
+ return new ScopedStore(data);
15053
+ }
15054
+ function createLazyCreatorState(state) {
15055
+ let _uniforms = null;
15056
+ let _nodes = null;
15057
+ let _buffers = null;
15058
+ let _gpuStorage = null;
15059
+ return Object.create(state, {
15060
+ uniforms: {
15061
+ get() {
15062
+ return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
15063
+ }
15064
+ },
15065
+ nodes: {
15066
+ get() {
15067
+ return _nodes ?? (_nodes = createScopedStore(state.nodes));
15068
+ }
15069
+ },
15070
+ buffers: {
15071
+ get() {
15072
+ return _buffers ?? (_buffers = createScopedStore(state.buffers));
15073
+ }
15074
+ },
15075
+ gpuStorage: {
15076
+ get() {
15077
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
15078
+ }
15079
+ }
15080
+ });
15081
+ }
15082
+
15083
+ function resolvePrimary(local) {
15084
+ return local.getState().primaryStore ?? local;
15085
+ }
15086
+ function usePrimaryStore() {
15087
+ const local = useStore();
15088
+ const primary = local.getState().primaryStore;
15089
+ return React.useMemo(() => primary ?? local, [primary, local]);
15090
+ }
15091
+ function usePrimaryThree(selector = (state) => state, equalityFn) {
15092
+ const local = useStore();
15093
+ const primary = resolvePrimary(local);
15094
+ return primary(selector, equalityFn);
15095
+ }
15096
+
15148
15097
  function addTexture(set, key, value) {
15149
15098
  set((state) => {
15150
15099
  const newMap = new Map(state.textures);
@@ -15184,6 +15133,27 @@ function createTextureOperations(set) {
15184
15133
  removeMultiple: (keys) => removeTextures(set, keys)
15185
15134
  };
15186
15135
  }
15136
+ function extractTSLValue(value) {
15137
+ if (value === null || value === void 0) return value;
15138
+ if (typeof value !== "object") return value;
15139
+ const node = value;
15140
+ if (!node.isNode) return value;
15141
+ if (node.isConstNode) {
15142
+ return node.value;
15143
+ }
15144
+ if ("value" in node) {
15145
+ let extractedValue = node.value;
15146
+ if (typeof node.traverse === "function") {
15147
+ node.traverse((n) => {
15148
+ if (n.isConstNode) {
15149
+ extractedValue = n.value;
15150
+ }
15151
+ });
15152
+ }
15153
+ return extractedValue;
15154
+ }
15155
+ return value;
15156
+ }
15187
15157
  function vectorize(inObject) {
15188
15158
  if (inObject === null || inObject === void 0) return inObject;
15189
15159
  if (typeof inObject === "string") {
@@ -15196,9 +15166,16 @@ function vectorize(inObject) {
15196
15166
  }
15197
15167
  if (typeof inObject !== "object") return inObject;
15198
15168
  const obj = inObject;
15169
+ if (obj.isNode) {
15170
+ return extractTSLValue(inObject);
15171
+ }
15199
15172
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15200
15173
  if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15201
15174
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15175
+ if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15176
+ const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
15177
+ return new webgpu.Color(obj.r * scale, obj.g * scale, obj.b * scale);
15178
+ }
15202
15179
  if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
15203
15180
  if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
15204
15181
  return new webgpu.Vector4(obj.x, obj.y, obj.z, obj.w);
@@ -15222,7 +15199,7 @@ function useCompareMemoize(value, deep) {
15222
15199
 
15223
15200
  const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15224
15201
  function useUniforms(creatorOrScope, scope) {
15225
- const store = useStore();
15202
+ const store = usePrimaryStore();
15226
15203
  const removeUniforms2 = React.useCallback(
15227
15204
  (names, targetScope) => {
15228
15205
  const nameArray = Array.isArray(names) ? names : [names];
@@ -15258,21 +15235,55 @@ function useUniforms(creatorOrScope, scope) {
15258
15235
  },
15259
15236
  [store]
15260
15237
  );
15238
+ const rebuildUniforms = React.useCallback(
15239
+ (targetScope) => {
15240
+ store.setState((state) => {
15241
+ let newUniforms = {};
15242
+ if (targetScope && targetScope !== "root") {
15243
+ const { [targetScope]: _, ...rest } = state.uniforms;
15244
+ newUniforms = rest;
15245
+ } else if (targetScope === "root") {
15246
+ for (const [key, value] of Object.entries(state.uniforms)) {
15247
+ if (!isUniformNode$1(value)) newUniforms[key] = value;
15248
+ }
15249
+ }
15250
+ return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15251
+ });
15252
+ },
15253
+ [store]
15254
+ );
15261
15255
  const inputForMemoization = React.useMemo(() => {
15262
- return is.fun(creatorOrScope) ? creatorOrScope(store.getState()) : creatorOrScope;
15256
+ let raw = creatorOrScope;
15257
+ if (is.fun(creatorOrScope)) {
15258
+ const wrappedState = createLazyCreatorState(store.getState());
15259
+ raw = creatorOrScope(wrappedState);
15260
+ }
15261
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
15262
+ const normalized = {};
15263
+ for (const [key, value] of Object.entries(raw)) {
15264
+ normalized[key] = vectorize(value);
15265
+ }
15266
+ return normalized;
15267
+ }
15268
+ return raw;
15263
15269
  }, [creatorOrScope, store]);
15264
15270
  const memoizedInput = useCompareMemoize(inputForMemoization);
15271
+ const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15272
+ const storeUniforms = usePrimaryThree((s) => s.uniforms);
15273
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15274
+ const readerDep = isReader ? storeUniforms : null;
15275
+ const creatorDep = isReader ? null : hmrVersion;
15265
15276
  const uniforms = React.useMemo(() => {
15266
- const state = store.getState();
15267
- const set = store.setState;
15268
15277
  if (memoizedInput === void 0) {
15269
- return state.uniforms;
15278
+ return storeUniforms;
15270
15279
  }
15271
15280
  if (typeof memoizedInput === "string") {
15272
- const scopeData = state.uniforms[memoizedInput];
15281
+ const scopeData = storeUniforms[memoizedInput];
15273
15282
  if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
15274
15283
  return {};
15275
15284
  }
15285
+ const state = store.getState();
15286
+ const set = store.setState;
15276
15287
  if (typeof memoizedInput !== "object" || memoizedInput === null) {
15277
15288
  throw new Error("Invalid uniform input");
15278
15289
  }
@@ -15316,8 +15327,23 @@ function useUniforms(creatorOrScope, scope) {
15316
15327
  }
15317
15328
  }
15318
15329
  return result;
15319
- }, [store, memoizedInput, scope]);
15320
- return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
15330
+ }, [store, memoizedInput, scope, readerDep, creatorDep]);
15331
+ return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15332
+ }
15333
+ function rebuildAllUniforms(store, scope) {
15334
+ store = store.getState().primaryStore ?? store;
15335
+ store.setState((state) => {
15336
+ let newUniforms = {};
15337
+ if (scope && scope !== "root") {
15338
+ const { [scope]: _, ...rest } = state.uniforms;
15339
+ newUniforms = rest;
15340
+ } else if (scope === "root") {
15341
+ for (const [key, value] of Object.entries(state.uniforms)) {
15342
+ if (!isUniformNode$1(value)) newUniforms[key] = value;
15343
+ }
15344
+ }
15345
+ return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15346
+ });
15321
15347
  }
15322
15348
  function removeUniforms(set, names, scope) {
15323
15349
  set((state) => {
@@ -15382,15 +15408,17 @@ function isSameThreeType(a, b) {
15382
15408
  }
15383
15409
 
15384
15410
  const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15411
+ const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15385
15412
  function useUniform(name, value) {
15386
15413
  const store = useStore();
15414
+ const hmrVersion = useThree((s) => s._hmrVersion);
15387
15415
  return React.useMemo(() => {
15388
15416
  const state = store.getState();
15389
15417
  const set = store.setState;
15390
15418
  const existing = state.uniforms[name];
15391
15419
  if (existing && isUniformNode(existing)) {
15392
- if (value !== void 0) {
15393
- existing.value = value;
15420
+ if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15421
+ existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
15394
15422
  }
15395
15423
  return existing;
15396
15424
  }
@@ -15399,7 +15427,24 @@ function useUniform(name, value) {
15399
15427
  `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15400
15428
  );
15401
15429
  }
15402
- const node = tsl.uniform(value);
15430
+ if (isUniformNode(value)) {
15431
+ const node2 = value;
15432
+ if (typeof node2.setName === "function") {
15433
+ node2.setName(name);
15434
+ }
15435
+ set((s) => ({
15436
+ uniforms: { ...s.uniforms, [name]: node2 }
15437
+ }));
15438
+ return node2;
15439
+ }
15440
+ let node;
15441
+ if (isTSLNode$1(value)) {
15442
+ node = tsl.uniform(value);
15443
+ } else if (typeof value === "string") {
15444
+ node = tsl.uniform(new webgpu.Color(value));
15445
+ } else {
15446
+ node = tsl.uniform(value);
15447
+ }
15403
15448
  if (typeof node.setName === "function") {
15404
15449
  node.setName(name);
15405
15450
  }
@@ -15410,12 +15455,12 @@ function useUniform(name, value) {
15410
15455
  }
15411
15456
  }));
15412
15457
  return node;
15413
- }, [store, name]);
15458
+ }, [store, name, hmrVersion]);
15414
15459
  }
15415
15460
 
15416
15461
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15417
15462
  function useNodes(creatorOrScope, scope) {
15418
- const store = useStore();
15463
+ const store = usePrimaryStore();
15419
15464
  const removeNodes2 = React.useCallback(
15420
15465
  (names, targetScope) => {
15421
15466
  const nameArray = Array.isArray(names) ? names : [names];
@@ -15451,19 +15496,46 @@ function useNodes(creatorOrScope, scope) {
15451
15496
  },
15452
15497
  [store]
15453
15498
  );
15499
+ const rebuildNodes = React.useCallback(
15500
+ (targetScope) => {
15501
+ store.setState((state) => {
15502
+ let newNodes = state.nodes;
15503
+ if (targetScope && targetScope !== "root") {
15504
+ const { [targetScope]: _, ...rest } = state.nodes;
15505
+ newNodes = rest;
15506
+ } else if (targetScope === "root") {
15507
+ newNodes = {};
15508
+ for (const [key, value] of Object.entries(state.nodes)) {
15509
+ if (!isTSLNode(value)) newNodes[key] = value;
15510
+ }
15511
+ } else {
15512
+ newNodes = {};
15513
+ }
15514
+ return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15515
+ });
15516
+ },
15517
+ [store]
15518
+ );
15519
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15520
+ const storeNodes = usePrimaryThree((s) => s.nodes);
15521
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15522
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15523
+ const readerDep = isReader ? storeNodes : null;
15524
+ const creatorDep = isReader ? null : hmrVersion;
15454
15525
  const nodes = React.useMemo(() => {
15455
- const state = store.getState();
15456
- const set = store.setState;
15457
15526
  if (creatorOrScope === void 0) {
15458
- return state.nodes;
15527
+ return storeNodes;
15459
15528
  }
15460
15529
  if (typeof creatorOrScope === "string") {
15461
- const scopeData = state.nodes[creatorOrScope];
15530
+ const scopeData = storeNodes[creatorOrScope];
15462
15531
  if (scopeData && !isTSLNode(scopeData)) return scopeData;
15463
15532
  return {};
15464
15533
  }
15534
+ const state = store.getState();
15535
+ const set = store.setState;
15465
15536
  const creator = creatorOrScope;
15466
- const created = creator(state);
15537
+ const wrappedState = createLazyCreatorState(state);
15538
+ const created = creator(wrappedState);
15467
15539
  const result = {};
15468
15540
  let hasNewNodes = false;
15469
15541
  if (scope) {
@@ -15472,7 +15544,7 @@ function useNodes(creatorOrScope, scope) {
15472
15544
  if (currentScope[name]) {
15473
15545
  result[name] = currentScope[name];
15474
15546
  } else {
15475
- if (typeof node.label === "function") node.setName(`${scope}.${name}`);
15547
+ node.setName?.(`${scope}.${name}`);
15476
15548
  result[name] = node;
15477
15549
  hasNewNodes = true;
15478
15550
  }
@@ -15492,7 +15564,7 @@ function useNodes(creatorOrScope, scope) {
15492
15564
  if (existing && isTSLNode(existing)) {
15493
15565
  result[name] = existing;
15494
15566
  } else {
15495
- if (typeof node.label === "function") node.setName(name);
15567
+ node.setName?.(name);
15496
15568
  result[name] = node;
15497
15569
  hasNewNodes = true;
15498
15570
  }
@@ -15501,8 +15573,26 @@ function useNodes(creatorOrScope, scope) {
15501
15573
  set((s) => ({ nodes: { ...s.nodes, ...result } }));
15502
15574
  }
15503
15575
  return result;
15504
- }, [store, typeof creatorOrScope === "string" ? creatorOrScope : scope]);
15505
- return { ...nodes, removeNodes: removeNodes2, clearNodes };
15576
+ }, [store, scopeDep, readerDep, creatorDep]);
15577
+ return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
15578
+ }
15579
+ function rebuildAllNodes(store, scope) {
15580
+ store = store.getState().primaryStore ?? store;
15581
+ store.setState((state) => {
15582
+ let newNodes = state.nodes;
15583
+ if (scope && scope !== "root") {
15584
+ const { [scope]: _, ...rest } = state.nodes;
15585
+ newNodes = rest;
15586
+ } else if (scope === "root") {
15587
+ newNodes = {};
15588
+ for (const [key, value] of Object.entries(state.nodes)) {
15589
+ if (!isTSLNode(value)) newNodes[key] = value;
15590
+ }
15591
+ } else {
15592
+ newNodes = {};
15593
+ }
15594
+ return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15595
+ });
15506
15596
  }
15507
15597
  function removeNodes(set, names, scope) {
15508
15598
  set((state) => {
@@ -15532,17 +15622,364 @@ function clearRootNodes(set) {
15532
15622
  });
15533
15623
  }
15534
15624
  function useLocalNodes(creator) {
15535
- const store = useStore();
15536
- const uniforms = useThree((s) => s.uniforms);
15537
- const nodes = useThree((s) => s.nodes);
15538
- const textures = useThree((s) => s.textures);
15625
+ const store = usePrimaryStore();
15626
+ const uniforms = usePrimaryThree((s) => s.uniforms);
15627
+ const nodes = usePrimaryThree((s) => s.nodes);
15628
+ const textures = usePrimaryThree((s) => s.textures);
15629
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15539
15630
  return React.useMemo(() => {
15631
+ const wrappedState = createLazyCreatorState(store.getState());
15632
+ return creator(wrappedState);
15633
+ }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15634
+ }
15635
+
15636
+ const isBufferLike = (value) => {
15637
+ if (value === null || typeof value !== "object") return false;
15638
+ if (ArrayBuffer.isView(value)) return true;
15639
+ if ("isBufferAttribute" in value) return true;
15640
+ if ("uuid" in value || "nodeType" in value) return true;
15641
+ return false;
15642
+ };
15643
+ const disposeBuffer = (buffer) => {
15644
+ if (buffer === null || typeof buffer !== "object") return;
15645
+ if ("dispose" in buffer && typeof buffer.dispose === "function") {
15646
+ buffer.dispose();
15647
+ }
15648
+ };
15649
+ function useBuffers(creatorOrScope, scope) {
15650
+ const store = usePrimaryStore();
15651
+ const removeBuffers = React.useCallback(
15652
+ (names, targetScope) => {
15653
+ const nameArray = Array.isArray(names) ? names : [names];
15654
+ store.setState((state) => {
15655
+ if (targetScope) {
15656
+ const currentScope = { ...state.buffers[targetScope] };
15657
+ for (const name of nameArray) delete currentScope[name];
15658
+ return { buffers: { ...state.buffers, [targetScope]: currentScope } };
15659
+ }
15660
+ const buffers2 = { ...state.buffers };
15661
+ for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
15662
+ return { buffers: buffers2 };
15663
+ });
15664
+ },
15665
+ [store]
15666
+ );
15667
+ const clearBuffers = React.useCallback(
15668
+ (targetScope) => {
15669
+ store.setState((state) => {
15670
+ if (targetScope && targetScope !== "root") {
15671
+ const { [targetScope]: _, ...rest } = state.buffers;
15672
+ return { buffers: rest };
15673
+ }
15674
+ if (targetScope === "root") {
15675
+ const buffers2 = {};
15676
+ for (const [key, value] of Object.entries(state.buffers)) {
15677
+ if (!isBufferLike(value)) buffers2[key] = value;
15678
+ }
15679
+ return { buffers: buffers2 };
15680
+ }
15681
+ return { buffers: {} };
15682
+ });
15683
+ },
15684
+ [store]
15685
+ );
15686
+ const rebuildBuffers = React.useCallback(
15687
+ (targetScope) => {
15688
+ store.setState((state) => {
15689
+ let newBuffers = state.buffers;
15690
+ if (targetScope && targetScope !== "root") {
15691
+ const { [targetScope]: _, ...rest } = state.buffers;
15692
+ newBuffers = rest;
15693
+ } else if (targetScope === "root") {
15694
+ newBuffers = {};
15695
+ for (const [key, value] of Object.entries(state.buffers)) {
15696
+ if (!isBufferLike(value)) newBuffers[key] = value;
15697
+ }
15698
+ } else {
15699
+ newBuffers = {};
15700
+ }
15701
+ return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15702
+ });
15703
+ },
15704
+ [store]
15705
+ );
15706
+ const disposeBuffers = React.useCallback(
15707
+ (names, targetScope) => {
15708
+ const nameArray = Array.isArray(names) ? names : [names];
15709
+ const state = store.getState();
15710
+ for (const name of nameArray) {
15711
+ const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
15712
+ if (buffer && isBufferLike(buffer)) {
15713
+ disposeBuffer(buffer);
15714
+ }
15715
+ }
15716
+ removeBuffers(names, targetScope);
15717
+ },
15718
+ [store, removeBuffers]
15719
+ );
15720
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15721
+ const storeBuffers = usePrimaryThree((s) => s.buffers);
15722
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15723
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15724
+ const readerDep = isReader ? storeBuffers : null;
15725
+ const creatorDep = isReader ? null : hmrVersion;
15726
+ const buffers = React.useMemo(() => {
15727
+ if (creatorOrScope === void 0) {
15728
+ return storeBuffers;
15729
+ }
15730
+ if (typeof creatorOrScope === "string") {
15731
+ const scopeData = storeBuffers[creatorOrScope];
15732
+ if (scopeData && !isBufferLike(scopeData)) return scopeData;
15733
+ return {};
15734
+ }
15735
+ const state = store.getState();
15736
+ const set = store.setState;
15737
+ const creator = creatorOrScope;
15738
+ const wrappedState = createLazyCreatorState(state);
15739
+ const created = creator(wrappedState);
15740
+ const result = {};
15741
+ let hasNewBuffers = false;
15742
+ if (scope) {
15743
+ const currentScope = state.buffers[scope] ?? {};
15744
+ for (const [name, buffer] of Object.entries(created)) {
15745
+ if (currentScope[name]) {
15746
+ result[name] = currentScope[name];
15747
+ } else {
15748
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15749
+ buffer.setName(`${scope}.${name}`);
15750
+ }
15751
+ result[name] = buffer;
15752
+ hasNewBuffers = true;
15753
+ }
15754
+ }
15755
+ if (hasNewBuffers) {
15756
+ set((s) => ({
15757
+ buffers: {
15758
+ ...s.buffers,
15759
+ [scope]: { ...s.buffers[scope], ...result }
15760
+ }
15761
+ }));
15762
+ }
15763
+ return result;
15764
+ }
15765
+ for (const [name, buffer] of Object.entries(created)) {
15766
+ const existing = state.buffers[name];
15767
+ if (existing && isBufferLike(existing)) {
15768
+ result[name] = existing;
15769
+ } else {
15770
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15771
+ buffer.setName(name);
15772
+ }
15773
+ result[name] = buffer;
15774
+ hasNewBuffers = true;
15775
+ }
15776
+ }
15777
+ if (hasNewBuffers) {
15778
+ set((s) => ({ buffers: { ...s.buffers, ...result } }));
15779
+ }
15780
+ return result;
15781
+ }, [store, scopeDep, readerDep, creatorDep]);
15782
+ return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
15783
+ }
15784
+ function rebuildAllBuffers(store, scope) {
15785
+ store = store.getState().primaryStore ?? store;
15786
+ store.setState((state) => {
15787
+ let newBuffers = state.buffers;
15788
+ if (scope && scope !== "root") {
15789
+ const { [scope]: _, ...rest } = state.buffers;
15790
+ newBuffers = rest;
15791
+ } else if (scope === "root") {
15792
+ newBuffers = {};
15793
+ for (const [key, value] of Object.entries(state.buffers)) {
15794
+ if (!isBufferLike(value)) newBuffers[key] = value;
15795
+ }
15796
+ } else {
15797
+ newBuffers = {};
15798
+ }
15799
+ return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15800
+ });
15801
+ }
15802
+
15803
+ const isStorageLike = (value) => {
15804
+ if (value === null || typeof value !== "object") return false;
15805
+ if ("isTexture" in value) return true;
15806
+ if ("isData3DTexture" in value) return true;
15807
+ if ("uuid" in value || "nodeType" in value) return true;
15808
+ return false;
15809
+ };
15810
+ const disposeStorage = (storage) => {
15811
+ if (storage === null || typeof storage !== "object") return;
15812
+ if ("dispose" in storage && typeof storage.dispose === "function") {
15813
+ storage.dispose();
15814
+ }
15815
+ };
15816
+ function useGPUStorage(creatorOrScope, scope) {
15817
+ const store = usePrimaryStore();
15818
+ const removeStorage = React.useCallback(
15819
+ (names, targetScope) => {
15820
+ const nameArray = Array.isArray(names) ? names : [names];
15821
+ store.setState((state) => {
15822
+ if (targetScope) {
15823
+ const currentScope = { ...state.gpuStorage[targetScope] };
15824
+ for (const name of nameArray) delete currentScope[name];
15825
+ return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
15826
+ }
15827
+ const gpuStorage2 = { ...state.gpuStorage };
15828
+ for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
15829
+ return { gpuStorage: gpuStorage2 };
15830
+ });
15831
+ },
15832
+ [store]
15833
+ );
15834
+ const clearStorage = React.useCallback(
15835
+ (targetScope) => {
15836
+ store.setState((state) => {
15837
+ if (targetScope && targetScope !== "root") {
15838
+ const { [targetScope]: _, ...rest } = state.gpuStorage;
15839
+ return { gpuStorage: rest };
15840
+ }
15841
+ if (targetScope === "root") {
15842
+ const gpuStorage2 = {};
15843
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15844
+ if (!isStorageLike(value)) gpuStorage2[key] = value;
15845
+ }
15846
+ return { gpuStorage: gpuStorage2 };
15847
+ }
15848
+ return { gpuStorage: {} };
15849
+ });
15850
+ },
15851
+ [store]
15852
+ );
15853
+ const rebuildStorage = React.useCallback(
15854
+ (targetScope) => {
15855
+ store.setState((state) => {
15856
+ let newStorage = state.gpuStorage;
15857
+ if (targetScope && targetScope !== "root") {
15858
+ const { [targetScope]: _, ...rest } = state.gpuStorage;
15859
+ newStorage = rest;
15860
+ } else if (targetScope === "root") {
15861
+ newStorage = {};
15862
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15863
+ if (!isStorageLike(value)) newStorage[key] = value;
15864
+ }
15865
+ } else {
15866
+ newStorage = {};
15867
+ }
15868
+ return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15869
+ });
15870
+ },
15871
+ [store]
15872
+ );
15873
+ const disposeStorageFn = React.useCallback(
15874
+ (names, targetScope) => {
15875
+ const nameArray = Array.isArray(names) ? names : [names];
15876
+ const state = store.getState();
15877
+ for (const name of nameArray) {
15878
+ const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
15879
+ if (storage && isStorageLike(storage)) {
15880
+ disposeStorage(storage);
15881
+ }
15882
+ }
15883
+ removeStorage(names, targetScope);
15884
+ },
15885
+ [store, removeStorage]
15886
+ );
15887
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15888
+ const storeStorage = usePrimaryThree((s) => s.gpuStorage);
15889
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15890
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15891
+ const readerDep = isReader ? storeStorage : null;
15892
+ const creatorDep = isReader ? null : hmrVersion;
15893
+ const gpuStorage = React.useMemo(() => {
15894
+ if (creatorOrScope === void 0) {
15895
+ return storeStorage;
15896
+ }
15897
+ if (typeof creatorOrScope === "string") {
15898
+ const scopeData = storeStorage[creatorOrScope];
15899
+ if (scopeData && !isStorageLike(scopeData)) return scopeData;
15900
+ return {};
15901
+ }
15540
15902
  const state = store.getState();
15541
- return creator(state);
15542
- }, [store, creator, uniforms, nodes, textures]);
15903
+ const set = store.setState;
15904
+ const creator = creatorOrScope;
15905
+ const wrappedState = createLazyCreatorState(state);
15906
+ const created = creator(wrappedState);
15907
+ const result = {};
15908
+ let hasNewStorage = false;
15909
+ if (scope) {
15910
+ const currentScope = state.gpuStorage[scope] ?? {};
15911
+ for (const [name, storage] of Object.entries(created)) {
15912
+ if (currentScope[name]) {
15913
+ result[name] = currentScope[name];
15914
+ } else {
15915
+ if ("setName" in storage && typeof storage.setName === "function") {
15916
+ storage.setName(`${scope}.${name}`);
15917
+ }
15918
+ if ("name" in storage && typeof storage.name === "string") {
15919
+ storage.name = `${scope}.${name}`;
15920
+ }
15921
+ result[name] = storage;
15922
+ hasNewStorage = true;
15923
+ }
15924
+ }
15925
+ if (hasNewStorage) {
15926
+ set((s) => ({
15927
+ gpuStorage: {
15928
+ ...s.gpuStorage,
15929
+ [scope]: { ...s.gpuStorage[scope], ...result }
15930
+ }
15931
+ }));
15932
+ }
15933
+ return result;
15934
+ }
15935
+ for (const [name, storage] of Object.entries(created)) {
15936
+ const existing = state.gpuStorage[name];
15937
+ if (existing && isStorageLike(existing)) {
15938
+ result[name] = existing;
15939
+ } else {
15940
+ if ("setName" in storage && typeof storage.setName === "function") {
15941
+ storage.setName(name);
15942
+ }
15943
+ if ("name" in storage && typeof storage.name === "string") {
15944
+ storage.name = name;
15945
+ }
15946
+ result[name] = storage;
15947
+ hasNewStorage = true;
15948
+ }
15949
+ }
15950
+ if (hasNewStorage) {
15951
+ set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
15952
+ }
15953
+ return result;
15954
+ }, [store, scopeDep, readerDep, creatorDep]);
15955
+ return {
15956
+ ...gpuStorage,
15957
+ removeStorage,
15958
+ clearStorage,
15959
+ rebuildStorage,
15960
+ disposeStorage: disposeStorageFn
15961
+ };
15962
+ }
15963
+ function rebuildAllStorage(store, scope) {
15964
+ store = store.getState().primaryStore ?? store;
15965
+ store.setState((state) => {
15966
+ let newStorage = state.gpuStorage;
15967
+ if (scope && scope !== "root") {
15968
+ const { [scope]: _, ...rest } = state.gpuStorage;
15969
+ newStorage = rest;
15970
+ } else if (scope === "root") {
15971
+ newStorage = {};
15972
+ for (const [key, value] of Object.entries(state.gpuStorage)) {
15973
+ if (!isStorageLike(value)) newStorage[key] = value;
15974
+ }
15975
+ } else {
15976
+ newStorage = {};
15977
+ }
15978
+ return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15979
+ });
15543
15980
  }
15544
15981
 
15545
- function usePostProcessing(mainCB, setupCB) {
15982
+ function useRenderPipeline(mainCB, setupCB) {
15546
15983
  const store = useStore();
15547
15984
  const { scene, camera, renderer, isLegacy } = useThree();
15548
15985
  const callbacksRanRef = React.useRef(false);
@@ -15552,12 +15989,16 @@ function usePostProcessing(mainCB, setupCB) {
15552
15989
  mainCBRef.current = mainCB;
15553
15990
  setupCBRef.current = setupCB;
15554
15991
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15992
+ React.useEffect(() => {
15993
+ callbacksRanRef.current = false;
15994
+ scenePassCacheRef.current = null;
15995
+ }, []);
15555
15996
  const clearPasses = React.useCallback(() => {
15556
15997
  store.setState({ passes: {} });
15557
15998
  }, [store]);
15558
15999
  const reset = React.useCallback(() => {
15559
16000
  store.setState({
15560
- postProcessing: null,
16001
+ renderPipeline: null,
15561
16002
  passes: {}
15562
16003
  });
15563
16004
  callbacksRanRef.current = false;
@@ -15570,13 +16011,13 @@ function usePostProcessing(mainCB, setupCB) {
15570
16011
  }, []);
15571
16012
  React.useLayoutEffect(() => {
15572
16013
  if (isLegacy) {
15573
- throw new Error("usePostProcessing is only available with WebGPU renderer. Set renderer prop on Canvas.");
16014
+ throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
15574
16015
  }
15575
16016
  if (!renderer || !scene || !camera) return;
15576
16017
  const state = store.getState();
15577
16018
  const set = store.setState;
15578
16019
  try {
15579
- let pp = state.postProcessing;
16020
+ let pp = state.renderPipeline;
15580
16021
  let currentPasses = { ...state.passes };
15581
16022
  let justCreatedPP = false;
15582
16023
  if (!pp) {
@@ -15593,7 +16034,7 @@ function usePostProcessing(mainCB, setupCB) {
15593
16034
  }
15594
16035
  currentPasses.scenePass = scenePass;
15595
16036
  if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
15596
- set({ postProcessing: pp, passes: currentPasses });
16037
+ set({ renderPipeline: pp, passes: currentPasses });
15597
16038
  const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
15598
16039
  if (shouldRunCallbacks) {
15599
16040
  if (setupCBRef.current) {
@@ -15615,33 +16056,41 @@ function usePostProcessing(mainCB, setupCB) {
15615
16056
  callbacksRanRef.current = true;
15616
16057
  }
15617
16058
  } catch (error) {
15618
- console.error("[usePostProcessing] Setup error:", error);
16059
+ console.error("[useRenderPipeline] Setup error:", error);
15619
16060
  }
15620
16061
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
15621
16062
  const passes = useThree((s) => s.passes);
15622
- const postProcessing = useThree((s) => s.postProcessing);
16063
+ const renderPipeline = useThree((s) => s.renderPipeline);
15623
16064
  return {
15624
16065
  passes,
15625
- postProcessing,
16066
+ renderPipeline,
15626
16067
  clearPasses,
15627
16068
  reset,
15628
16069
  rebuild,
15629
- // isReady indicates if PostProcessing is configured and ready for rendering
15630
- isReady: postProcessing !== null
16070
+ // isReady indicates if RenderPipeline is configured and ready for rendering
16071
+ isReady: renderPipeline !== null
15631
16072
  };
15632
16073
  }
15633
16074
 
15634
16075
  extend(THREE);
15635
16076
 
16077
+ exports.Scheduler = scheduler.Scheduler;
16078
+ exports.getScheduler = scheduler.getScheduler;
15636
16079
  exports.Block = Block;
15637
16080
  exports.Canvas = Canvas;
16081
+ exports.Environment = Environment;
16082
+ exports.EnvironmentCube = EnvironmentCube;
16083
+ exports.EnvironmentMap = EnvironmentMap;
16084
+ exports.EnvironmentPortal = EnvironmentPortal;
15638
16085
  exports.ErrorBoundary = ErrorBoundary;
16086
+ exports.FROM_REF = FROM_REF;
15639
16087
  exports.IsObject = IsObject;
16088
+ exports.ONCE = ONCE;
16089
+ exports.Portal = Portal;
15640
16090
  exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
15641
16091
  exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
15642
16092
  exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
15643
16093
  exports.RESERVED_PROPS = RESERVED_PROPS;
15644
- exports.Scheduler = Scheduler;
15645
16094
  exports.Texture = Texture;
15646
16095
  exports._roots = _roots;
15647
16096
  exports.act = act;
@@ -15662,6 +16111,7 @@ exports.createEvents = createEvents;
15662
16111
  exports.createPointerEvents = createPointerEvents;
15663
16112
  exports.createPortal = createPortal;
15664
16113
  exports.createRoot = createRoot;
16114
+ exports.createScopedStore = createScopedStore;
15665
16115
  exports.createStore = createStore;
15666
16116
  exports.createTextureOperations = createTextureOperations;
15667
16117
  exports.detach = detach;
@@ -15671,33 +16121,49 @@ exports.events = createPointerEvents;
15671
16121
  exports.extend = extend;
15672
16122
  exports.findInitialRoot = findInitialRoot;
15673
16123
  exports.flushSync = flushSync;
16124
+ exports.fromRef = fromRef;
15674
16125
  exports.getInstanceProps = getInstanceProps;
16126
+ exports.getPrimary = getPrimary;
16127
+ exports.getPrimaryIds = getPrimaryIds;
15675
16128
  exports.getRootState = getRootState;
15676
- exports.getScheduler = getScheduler;
15677
16129
  exports.getUuidPrefix = getUuidPrefix;
15678
16130
  exports.hasConstructor = hasConstructor;
16131
+ exports.hasPrimary = hasPrimary;
15679
16132
  exports.invalidate = invalidate;
15680
16133
  exports.invalidateInstance = invalidateInstance;
15681
16134
  exports.is = is;
15682
16135
  exports.isColorRepresentation = isColorRepresentation;
15683
16136
  exports.isCopyable = isCopyable;
16137
+ exports.isFromRef = isFromRef;
15684
16138
  exports.isObject3D = isObject3D;
16139
+ exports.isOnce = isOnce;
15685
16140
  exports.isOrthographicCamera = isOrthographicCamera;
15686
16141
  exports.isRef = isRef;
15687
16142
  exports.isRenderer = isRenderer;
15688
16143
  exports.isTexture = isTexture;
15689
16144
  exports.isVectorLike = isVectorLike;
16145
+ exports.once = once;
15690
16146
  exports.prepare = prepare;
16147
+ exports.presetsObj = presetsObj;
16148
+ exports.rebuildAllBuffers = rebuildAllBuffers;
16149
+ exports.rebuildAllNodes = rebuildAllNodes;
16150
+ exports.rebuildAllStorage = rebuildAllStorage;
16151
+ exports.rebuildAllUniforms = rebuildAllUniforms;
15691
16152
  exports.reconciler = reconciler;
16153
+ exports.registerPrimary = registerPrimary;
15692
16154
  exports.removeInteractivity = removeInteractivity;
15693
16155
  exports.removeNodes = removeNodes;
15694
16156
  exports.removeUniforms = removeUniforms;
15695
16157
  exports.resolve = resolve;
15696
16158
  exports.unmountComponentAtNode = unmountComponentAtNode;
16159
+ exports.unregisterPrimary = unregisterPrimary;
15697
16160
  exports.updateCamera = updateCamera;
15698
16161
  exports.updateFrustum = updateFrustum;
15699
16162
  exports.useBridge = useBridge;
16163
+ exports.useBuffers = useBuffers;
16164
+ exports.useEnvironment = useEnvironment;
15700
16165
  exports.useFrame = useFrame;
16166
+ exports.useGPUStorage = useGPUStorage;
15701
16167
  exports.useGraph = useGraph;
15702
16168
  exports.useInstanceHandle = useInstanceHandle;
15703
16169
  exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
@@ -15705,7 +16171,7 @@ exports.useLoader = useLoader;
15705
16171
  exports.useLocalNodes = useLocalNodes;
15706
16172
  exports.useMutableCallback = useMutableCallback;
15707
16173
  exports.useNodes = useNodes;
15708
- exports.usePostProcessing = usePostProcessing;
16174
+ exports.useRenderPipeline = useRenderPipeline;
15709
16175
  exports.useRenderTarget = useRenderTarget;
15710
16176
  exports.useStore = useStore;
15711
16177
  exports.useTexture = useTexture;
@@ -15713,3 +16179,4 @@ exports.useTextures = useTextures;
15713
16179
  exports.useThree = useThree;
15714
16180
  exports.useUniform = useUniform;
15715
16181
  exports.useUniforms = useUniforms;
16182
+ exports.waitForPrimary = waitForPrimary;