@react-three/fiber 10.0.0-alpha.0 → 10.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -11,6 +11,7 @@ const Tb = require('scheduler');
11
11
  const traditional = require('zustand/traditional');
12
12
  const suspendReact = require('suspend-react');
13
13
 
14
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
15
16
 
16
17
  function _interopNamespaceCompat(e) {
@@ -51,6 +52,8 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
51
52
  Inspector: Inspector_js.Inspector,
52
53
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
53
54
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
55
+ RenderTargetCompat: webgpu.RenderTarget,
56
+ WebGLRenderTarget: three.WebGLRenderTarget,
54
57
  WebGLRenderer: three.WebGLRenderer
55
58
  }, [webgpu__namespace]);
56
59
 
@@ -159,6 +162,13 @@ function updateCamera(camera, size) {
159
162
  }
160
163
  camera.updateProjectionMatrix();
161
164
  }
165
+ const frustumMatrix = new webgpu.Matrix4();
166
+ function updateFrustum(camera, frustum) {
167
+ const target = frustum ?? new webgpu.Frustum();
168
+ frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
169
+ target.setFromProjectionMatrix(frustumMatrix);
170
+ return target;
171
+ }
162
172
 
163
173
  const REACT_INTERNAL_PROPS = ["children", "key", "ref"];
164
174
  function findInitialRoot(instance) {
@@ -239,6 +249,205 @@ function invalidateInstance(instance) {
239
249
  if (state && state.internal.frames === 0) state.invalidate();
240
250
  }
241
251
 
252
+ const tempFrustum = new webgpu.Frustum();
253
+ let hasWarnedWebGL = false;
254
+ let tslModule = null;
255
+ async function loadTSL() {
256
+ if (tslModule) return tslModule;
257
+ try {
258
+ const tsl = await import('three/tsl');
259
+ tslModule = { uniform: tsl.uniform, nodeObject: tsl.nodeObject };
260
+ return tslModule;
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+ function createOcclusionObserverNode(store, uniform) {
266
+ const node = new webgpu.Node("float");
267
+ node.updateType = webgpu.NodeUpdateType.OBJECT;
268
+ node.update = function(frame) {
269
+ const { internal } = store.getState();
270
+ const registry = internal.visibilityRegistry;
271
+ const cache = internal.occlusionCache;
272
+ for (const entry of registry.values()) {
273
+ const { object, handlers } = entry;
274
+ if (handlers.onOccluded || handlers.onVisible) {
275
+ const isOccluded = frame.renderer.isOccluded(object);
276
+ cache.set(object, isOccluded);
277
+ }
278
+ }
279
+ };
280
+ node.setup = function() {
281
+ return uniform(0);
282
+ };
283
+ return node;
284
+ }
285
+ let occlusionSetupPromise = null;
286
+ function enableOcclusion(store) {
287
+ const state = store.getState();
288
+ const { internal, renderer, rootScene } = state;
289
+ if (internal.occlusionEnabled || occlusionSetupPromise) return;
290
+ const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
291
+ if (!hasOcclusionSupport) {
292
+ if (!hasWarnedWebGL) {
293
+ console.warn(
294
+ "[R3F] Warning: onOccluded/onVisible occlusion queries require WebGPU renderer. Occlusion events will not fire on WebGL."
295
+ );
296
+ hasWarnedWebGL = true;
297
+ }
298
+ return;
299
+ }
300
+ occlusionSetupPromise = setupOcclusion(store);
301
+ }
302
+ async function setupOcclusion(store) {
303
+ const state = store.getState();
304
+ const { internal, rootScene, set } = state;
305
+ const tsl = await loadTSL();
306
+ if (!tsl) {
307
+ console.warn("[R3F] Warning: TSL module not available. Occlusion queries disabled.");
308
+ occlusionSetupPromise = null;
309
+ return;
310
+ }
311
+ const { uniform, nodeObject } = tsl;
312
+ let helperGroup = internal.helperGroup;
313
+ if (!helperGroup) {
314
+ helperGroup = new webgpu.Group();
315
+ helperGroup.name = "__r3fInternal";
316
+ helperGroup.__r3fInternal = true;
317
+ rootScene.add(helperGroup);
318
+ }
319
+ const geometry = new webgpu.BoxGeometry(1, 1, 1);
320
+ const material = new webgpu.MeshBasicNodeMaterial({
321
+ transparent: true,
322
+ opacity: 0
323
+ });
324
+ const observerNode = nodeObject(createOcclusionObserverNode(store, uniform));
325
+ material.colorNode = observerNode;
326
+ material.needsUpdate = true;
327
+ const mesh = new webgpu.Mesh(geometry, material);
328
+ mesh.name = "__r3fOcclusionObserver";
329
+ mesh.scale.setScalar(1e-4);
330
+ mesh.frustumCulled = false;
331
+ mesh.__r3fInternal = true;
332
+ helperGroup.add(mesh);
333
+ set((state2) => ({
334
+ internal: {
335
+ ...state2.internal,
336
+ helperGroup,
337
+ occlusionObserver: mesh,
338
+ occlusionEnabled: true
339
+ }
340
+ }));
341
+ occlusionSetupPromise = null;
342
+ }
343
+ function disableOcclusion(store) {
344
+ const { internal, set } = store.getState();
345
+ if (!internal.occlusionEnabled) return;
346
+ if (internal.occlusionObserver) {
347
+ internal.occlusionObserver.removeFromParent();
348
+ internal.occlusionObserver.geometry.dispose();
349
+ internal.occlusionObserver.material.dispose();
350
+ }
351
+ internal.occlusionCache.clear();
352
+ set((state) => ({
353
+ internal: {
354
+ ...state.internal,
355
+ occlusionObserver: null,
356
+ occlusionEnabled: false
357
+ }
358
+ }));
359
+ }
360
+ function cleanupHelperGroup(store) {
361
+ const { internal, set } = store.getState();
362
+ disableOcclusion(store);
363
+ if (internal.helperGroup) {
364
+ internal.helperGroup.removeFromParent();
365
+ set((state) => ({
366
+ internal: {
367
+ ...state.internal,
368
+ helperGroup: null
369
+ }
370
+ }));
371
+ }
372
+ }
373
+ function registerVisibility(store, object, handlers) {
374
+ const { internal } = store.getState();
375
+ const registry = internal.visibilityRegistry;
376
+ const entry = {
377
+ object,
378
+ handlers,
379
+ lastFramedState: null,
380
+ lastOccludedState: null,
381
+ lastVisibleState: null
382
+ };
383
+ registry.set(object.uuid, entry);
384
+ if (handlers.onOccluded || handlers.onVisible) {
385
+ object.occlusionTest = true;
386
+ if (!internal.occlusionEnabled) {
387
+ enableOcclusion(store);
388
+ }
389
+ }
390
+ }
391
+ function unregisterVisibility(store, object) {
392
+ const { internal } = store.getState();
393
+ internal.visibilityRegistry.delete(object.uuid);
394
+ internal.occlusionCache.delete(object);
395
+ }
396
+ function checkVisibility(state) {
397
+ const { internal, camera } = state;
398
+ const registry = internal.visibilityRegistry;
399
+ if (registry.size === 0) return;
400
+ updateFrustum(camera, tempFrustum);
401
+ for (const entry of registry.values()) {
402
+ const { object, handlers, lastFramedState, lastOccludedState, lastVisibleState } = entry;
403
+ let inFrustum = null;
404
+ const computeFrustum = () => {
405
+ if (inFrustum === null) {
406
+ if (object.geometry?.boundingSphere === null) {
407
+ object.geometry?.computeBoundingSphere();
408
+ }
409
+ inFrustum = tempFrustum.intersectsObject(object);
410
+ }
411
+ return inFrustum;
412
+ };
413
+ if (handlers.onFramed) {
414
+ const currentInFrustum = computeFrustum();
415
+ if (currentInFrustum !== lastFramedState) {
416
+ entry.lastFramedState = currentInFrustum;
417
+ handlers.onFramed(currentInFrustum);
418
+ }
419
+ }
420
+ let currentOcclusion = null;
421
+ if (handlers.onOccluded && internal.occlusionEnabled) {
422
+ currentOcclusion = internal.occlusionCache.get(object) ?? null;
423
+ if (currentOcclusion !== null && currentOcclusion !== lastOccludedState) {
424
+ entry.lastOccludedState = currentOcclusion;
425
+ handlers.onOccluded(currentOcclusion);
426
+ }
427
+ }
428
+ if (handlers.onVisible) {
429
+ const currentInFrustum = computeFrustum();
430
+ if (!handlers.onFramed && currentInFrustum !== lastFramedState) {
431
+ entry.lastFramedState = currentInFrustum;
432
+ }
433
+ let isOccluded = currentOcclusion;
434
+ if (isOccluded === null && internal.occlusionEnabled) {
435
+ isOccluded = internal.occlusionCache.get(object) ?? null;
436
+ }
437
+ if (isOccluded === null) isOccluded = false;
438
+ const isVisible = currentInFrustum && !isOccluded && object.visible;
439
+ if (isVisible !== lastVisibleState) {
440
+ entry.lastVisibleState = isVisible;
441
+ handlers.onVisible(isVisible);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ function hasVisibilityHandlers(handlers) {
447
+ if (!handlers) return false;
448
+ return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
449
+ }
450
+
242
451
  const RESERVED_PROPS = [
243
452
  "children",
244
453
  "key",
@@ -253,6 +462,7 @@ const RESERVED_PROPS = [
253
462
  "dispose"
254
463
  ];
255
464
  const EVENT_REGEX = /^on(Pointer|Drag|Drop|Click|DoubleClick|ContextMenu|Wheel)/;
465
+ const VISIBILITY_EVENT_REGEX = /^on(Framed|Occluded|Visible)$/;
256
466
  const INDEX_REGEX = /-\d+$/;
257
467
  const MEMOIZED_PROTOTYPES = /* @__PURE__ */ new Map();
258
468
  const colorMaps = ["map", "emissiveMap", "sheenColorMap", "specularColorMap", "envMap"];
@@ -339,7 +549,7 @@ function applyProps(object, props) {
339
549
  const rootState = instance && findInitialRoot(instance).getState();
340
550
  const prevHandlers = instance?.eventCount;
341
551
  for (const prop in props) {
342
- let value = props[prop];
552
+ const value = props[prop];
343
553
  if (RESERVED_PROPS.includes(prop)) continue;
344
554
  if (instance && EVENT_REGEX.test(prop)) {
345
555
  if (typeof value === "function") instance.handlers[prop] = value;
@@ -347,6 +557,12 @@ function applyProps(object, props) {
347
557
  instance.eventCount = Object.keys(instance.handlers).length;
348
558
  continue;
349
559
  }
560
+ if (instance && VISIBILITY_EVENT_REGEX.test(prop)) {
561
+ if (typeof value === "function") instance.handlers[prop] = value;
562
+ else delete instance.handlers[prop];
563
+ instance.eventCount = Object.keys(instance.handlers).length;
564
+ continue;
565
+ }
350
566
  if (value === void 0) continue;
351
567
  let { root, key, target } = resolve(object, prop);
352
568
  if (target === void 0 && (typeof root !== "object" || root === null)) {
@@ -383,6 +599,17 @@ function applyProps(object, props) {
383
599
  if (instance.eventCount && object2.raycast !== null) {
384
600
  rootState.internal.interaction.push(object2);
385
601
  }
602
+ const root = findInitialRoot(instance);
603
+ const visibilityHandlers = {
604
+ onFramed: instance.handlers.onFramed,
605
+ onOccluded: instance.handlers.onOccluded,
606
+ onVisible: instance.handlers.onVisible
607
+ };
608
+ if (hasVisibilityHandlers(visibilityHandlers)) {
609
+ registerVisibility(root, object2, visibilityHandlers);
610
+ } else {
611
+ unregisterVisibility(root, object2);
612
+ }
386
613
  }
387
614
  if (instance && instance.props.attach === void 0) {
388
615
  if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
@@ -417,6 +644,7 @@ function removeInteractivity(store, object) {
417
644
  internal.capturedMap.forEach((captures, pointerId) => {
418
645
  releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
419
646
  });
647
+ unregisterVisibility(store, object);
420
648
  }
421
649
  function createEvents(store) {
422
650
  function calculateDistance(event) {
@@ -473,13 +701,14 @@ function createEvents(store) {
473
701
  for (const hit of hits) {
474
702
  let eventObject = hit.object;
475
703
  while (eventObject) {
476
- if (eventObject.__r3f?.eventCount)
704
+ if (eventObject.__r3f?.eventCount) {
477
705
  intersections.push({ ...hit, eventObject });
706
+ }
478
707
  eventObject = eventObject.parent;
479
708
  }
480
709
  }
481
710
  if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
482
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
711
+ for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
483
712
  if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
484
713
  }
485
714
  }
@@ -509,12 +738,12 @@ function createEvents(store) {
509
738
  releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
510
739
  }
511
740
  };
512
- let extractEventProps = {};
513
- for (let prop in event) {
514
- let property = event[prop];
741
+ const extractEventProps = {};
742
+ for (const prop in event) {
743
+ const property = event[prop];
515
744
  if (typeof property !== "function") extractEventProps[prop] = property;
516
745
  }
517
- let raycastEvent = {
746
+ const raycastEvent = {
518
747
  ...hit,
519
748
  ...extractEventProps,
520
749
  pointer,
@@ -799,8 +1028,21 @@ function formatLocation(url, line) {
799
1028
  const file = clean.split("/").pop() ?? clean;
800
1029
  return `${file}:${line}`;
801
1030
  }
1031
+ function notifyAlpha({ message, link }) {
1032
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
1033
+ return;
1034
+ }
1035
+ if (shownNotices.has(message)) return;
1036
+ shownNotices.add(message);
1037
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
1038
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
1039
+ {
1040
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
1041
+ }
1042
+ }
802
1043
 
803
- const context = /* @__PURE__ */ React__namespace.createContext(null);
1044
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1045
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
804
1046
  const createStore = (invalidate, advance) => {
805
1047
  const rootStore = traditional.createWithEqualityFn((set, get) => {
806
1048
  const position = new webgpu.Vector3();
@@ -831,6 +1073,8 @@ const createStore = (invalidate, advance) => {
831
1073
  gl: null,
832
1074
  renderer: null,
833
1075
  camera: null,
1076
+ frustum: new webgpu.Frustum(),
1077
+ autoUpdateFrustum: true,
834
1078
  raycaster: null,
835
1079
  events: { priority: 1, enabled: true, connected: false },
836
1080
  scene: null,
@@ -882,10 +1126,38 @@ const createStore = (invalidate, advance) => {
882
1126
  getCurrentViewport
883
1127
  },
884
1128
  setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
885
- setSize: (width, height, top = 0, left = 0) => {
886
- const camera = get().camera;
887
- const size = { width, height, top, left };
888
- set((state2) => ({ size, viewport: { ...state2.viewport, ...getCurrentViewport(camera, defaultTarget, size) } }));
1129
+ setSize: (width, height, top, left) => {
1130
+ const state2 = get();
1131
+ if (width === void 0) {
1132
+ set({ _sizeImperative: false });
1133
+ if (state2._sizeProps) {
1134
+ const { width: propW, height: propH } = state2._sizeProps;
1135
+ if (propW !== void 0 || propH !== void 0) {
1136
+ const currentSize = state2.size;
1137
+ const newSize = {
1138
+ width: propW ?? currentSize.width,
1139
+ height: propH ?? currentSize.height,
1140
+ top: currentSize.top,
1141
+ left: currentSize.left
1142
+ };
1143
+ set((s) => ({
1144
+ size: newSize,
1145
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1146
+ }));
1147
+ }
1148
+ }
1149
+ return;
1150
+ }
1151
+ const w = width;
1152
+ const h = height ?? width;
1153
+ const t = top ?? state2.size.top;
1154
+ const l = left ?? state2.size.left;
1155
+ const size = { width: w, height: h, top: t, left: l };
1156
+ set((s) => ({
1157
+ size,
1158
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1159
+ _sizeImperative: true
1160
+ }));
889
1161
  },
890
1162
  setDpr: (dpr) => set((state2) => {
891
1163
  const resolved = calculateDpr(dpr);
@@ -902,6 +1174,9 @@ const createStore = (invalidate, advance) => {
902
1174
  textures: /* @__PURE__ */ new Map(),
903
1175
  postProcessing: null,
904
1176
  passes: {},
1177
+ _hmrVersion: 0,
1178
+ _sizeImperative: false,
1179
+ _sizeProps: null,
905
1180
  previousRoot: void 0,
906
1181
  internal: {
907
1182
  // Events
@@ -912,6 +1187,13 @@ const createStore = (invalidate, advance) => {
912
1187
  initialHits: [],
913
1188
  capturedMap: /* @__PURE__ */ new Map(),
914
1189
  lastEvent: React__namespace.createRef(),
1190
+ // Visibility tracking (onFramed, onOccluded, onVisible)
1191
+ visibilityRegistry: /* @__PURE__ */ new Map(),
1192
+ // Occlusion system (WebGPU only)
1193
+ occlusionEnabled: false,
1194
+ occlusionObserver: null,
1195
+ occlusionCache: /* @__PURE__ */ new Map(),
1196
+ helperGroup: null,
915
1197
  // Updates
916
1198
  active: false,
917
1199
  frames: 0,
@@ -997,7 +1279,15 @@ const createStore = (invalidate, advance) => {
997
1279
  }
998
1280
  if (camera !== oldCamera) {
999
1281
  oldCamera = camera;
1282
+ const { rootScene } = rootStore.getState();
1283
+ if (camera && rootScene && !camera.parent) {
1284
+ rootScene.add(camera);
1285
+ }
1000
1286
  set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1287
+ const currentState = rootStore.getState();
1288
+ if (currentState.autoUpdateFrustum && camera) {
1289
+ updateFrustum(camera, currentState.frustum);
1290
+ }
1001
1291
  }
1002
1292
  });
1003
1293
  rootStore.subscribe((state2) => invalidate(state2));
@@ -1094,8 +1384,9 @@ class PhaseGraph {
1094
1384
  const node = { name, isAutoGenerated: false };
1095
1385
  let insertIndex = this.phases.length;
1096
1386
  const targetIndex = this.getPhaseIndex(before ?? after);
1097
- if (targetIndex !== -1) insertIndex = before ? targetIndex : targetIndex + 1;
1098
- else {
1387
+ if (targetIndex !== -1) {
1388
+ insertIndex = before ? targetIndex : targetIndex + 1;
1389
+ } else {
1099
1390
  const constraintType = before ? "before" : "after";
1100
1391
  console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
1101
1392
  }
@@ -1361,6 +1652,10 @@ const _Scheduler = class _Scheduler {
1361
1652
  __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1362
1653
  __publicField(this, "pendingFrames", 0);
1363
1654
  __publicField(this, "_frameloop", "always");
1655
+ //* Independent Mode & Error Handling State ================================
1656
+ __publicField(this, "_independent", false);
1657
+ __publicField(this, "errorHandler", null);
1658
+ __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1364
1659
  //* Core Loop Execution Methods ================================
1365
1660
  /**
1366
1661
  * Main RAF loop callback.
@@ -1383,6 +1678,12 @@ const _Scheduler = class _Scheduler {
1383
1678
  });
1384
1679
  this.phaseGraph = new PhaseGraph();
1385
1680
  }
1681
+ static get instance() {
1682
+ return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1683
+ }
1684
+ static set instance(value) {
1685
+ globalThis[_Scheduler.INSTANCE_KEY] = value;
1686
+ }
1386
1687
  /**
1387
1688
  * Get the global scheduler instance (creates if doesn't exist).
1388
1689
  * Uses HMR data to preserve instance across hot reloads.
@@ -1431,29 +1732,43 @@ const _Scheduler = class _Scheduler {
1431
1732
  get isRunning() {
1432
1733
  return this.loopState.running;
1433
1734
  }
1735
+ get isReady() {
1736
+ return this.roots.size > 0;
1737
+ }
1738
+ get independent() {
1739
+ return this._independent;
1740
+ }
1741
+ set independent(value) {
1742
+ this._independent = value;
1743
+ if (value) this.ensureDefaultRoot();
1744
+ }
1434
1745
  //* Root Management Methods ================================
1435
1746
  /**
1436
1747
  * Register a root (Canvas) with the scheduler.
1437
1748
  * The first root to register starts the RAF loop (if frameloop='always').
1438
1749
  * @param {string} id - Unique identifier for this root
1439
- * @param {() => RootState} getState - Function to get the root's current state
1750
+ * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1440
1751
  * @returns {() => void} Unsubscribe function to remove this root
1441
1752
  */
1442
- registerRoot(id, getState) {
1753
+ registerRoot(id, options = {}) {
1443
1754
  if (this.roots.has(id)) {
1444
1755
  console.warn(`[Scheduler] Root "${id}" already registered`);
1445
1756
  return () => this.unregisterRoot(id);
1446
1757
  }
1447
1758
  const entry = {
1448
1759
  id,
1449
- getState,
1760
+ getState: options.getState ?? (() => ({})),
1450
1761
  jobs: /* @__PURE__ */ new Map(),
1451
1762
  sortedJobs: [],
1452
1763
  needsRebuild: false
1453
1764
  };
1765
+ if (options.onError) {
1766
+ this.errorHandler = options.onError;
1767
+ }
1454
1768
  this.roots.set(id, entry);
1455
- if (this.roots.size === 1 && this._frameloop === "always") {
1456
- this.start();
1769
+ if (this.roots.size === 1) {
1770
+ this.notifyRootReady();
1771
+ if (this._frameloop === "always") this.start();
1457
1772
  }
1458
1773
  return () => this.unregisterRoot(id);
1459
1774
  }
@@ -1473,7 +1788,60 @@ const _Scheduler = class _Scheduler {
1473
1788
  this.roots.delete(id);
1474
1789
  if (this.roots.size === 0) {
1475
1790
  this.stop();
1791
+ this.errorHandler = null;
1792
+ }
1793
+ }
1794
+ /**
1795
+ * Subscribe to be notified when a root becomes available.
1796
+ * Fires immediately if a root already exists.
1797
+ * @param {() => void} callback - Function called when first root registers
1798
+ * @returns {() => void} Unsubscribe function
1799
+ */
1800
+ onRootReady(callback) {
1801
+ if (this.roots.size > 0) {
1802
+ callback();
1803
+ return () => {
1804
+ };
1805
+ }
1806
+ this.rootReadyCallbacks.add(callback);
1807
+ return () => this.rootReadyCallbacks.delete(callback);
1808
+ }
1809
+ /**
1810
+ * Notify all registered root-ready callbacks.
1811
+ * Called when the first root registers.
1812
+ * @returns {void}
1813
+ * @private
1814
+ */
1815
+ notifyRootReady() {
1816
+ for (const cb of this.rootReadyCallbacks) {
1817
+ try {
1818
+ cb();
1819
+ } catch (error) {
1820
+ console.error("[Scheduler] Error in root-ready callback:", error);
1821
+ }
1476
1822
  }
1823
+ this.rootReadyCallbacks.clear();
1824
+ }
1825
+ /**
1826
+ * Ensure a default root exists for independent mode.
1827
+ * Creates a minimal root with no state provider.
1828
+ * @returns {void}
1829
+ * @private
1830
+ */
1831
+ ensureDefaultRoot() {
1832
+ if (!this.roots.has("__default__")) {
1833
+ this.registerRoot("__default__");
1834
+ }
1835
+ }
1836
+ /**
1837
+ * Trigger error handling for job errors.
1838
+ * Uses the bound error handler if available, otherwise logs to console.
1839
+ * @param {Error} error - The error to handle
1840
+ * @returns {void}
1841
+ */
1842
+ triggerError(error) {
1843
+ if (this.errorHandler) this.errorHandler(error);
1844
+ else console.error("[Scheduler]", error);
1477
1845
  }
1478
1846
  //* Phase Management Methods ================================
1479
1847
  /**
@@ -1833,9 +2201,9 @@ const _Scheduler = class _Scheduler {
1833
2201
  const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
1834
2202
  const delta = deltaMs / 1e3;
1835
2203
  const elapsed = now - this.loopState.createdAt;
1836
- const rootState = root.getState();
2204
+ const providedState = root.getState?.() ?? {};
1837
2205
  const frameState = {
1838
- ...rootState,
2206
+ ...providedState,
1839
2207
  time: now,
1840
2208
  delta,
1841
2209
  elapsed,
@@ -1845,6 +2213,7 @@ const _Scheduler = class _Scheduler {
1845
2213
  job.callback(frameState, delta);
1846
2214
  } catch (error) {
1847
2215
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
2216
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1848
2217
  }
1849
2218
  }
1850
2219
  /**
@@ -1886,7 +2255,7 @@ const _Scheduler = class _Scheduler {
1886
2255
  /**
1887
2256
  * Execute all jobs for a single root in sorted order.
1888
2257
  * Rebuilds sorted job list if needed, then dispatches each job.
1889
- * Errors are caught and propagated to the root's error boundary.
2258
+ * Errors are caught and propagated via triggerError.
1890
2259
  * @param {RootEntry} root - The root entry to tick
1891
2260
  * @param {number} timestamp - RAF timestamp in milliseconds
1892
2261
  * @param {number} delta - Time since last frame in seconds
@@ -1898,10 +2267,9 @@ const _Scheduler = class _Scheduler {
1898
2267
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
1899
2268
  root.needsRebuild = false;
1900
2269
  }
1901
- const rootState = root.getState();
1902
- if (!rootState) return;
2270
+ const providedState = root.getState?.() ?? {};
1903
2271
  const frameState = {
1904
- ...rootState,
2272
+ ...providedState,
1905
2273
  time: timestamp,
1906
2274
  delta,
1907
2275
  elapsed: this.loopState.elapsedTime / 1e3,
@@ -1914,7 +2282,7 @@ const _Scheduler = class _Scheduler {
1914
2282
  job.callback(frameState, delta);
1915
2283
  } catch (error) {
1916
2284
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
1917
- rootState.setError(error instanceof Error ? error : new Error(String(error)));
2285
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1918
2286
  }
1919
2287
  }
1920
2288
  }
@@ -1999,8 +2367,11 @@ const _Scheduler = class _Scheduler {
1999
2367
  return /* @__PURE__ */ new Set([value]);
2000
2368
  }
2001
2369
  };
2002
- //* Static State & Methods (Singlton Usage) ================================
2003
- __publicField(_Scheduler, "instance", null);
2370
+ //* Static State & Methods (Singleton Usage) ================================
2371
+ //* Cross-Bundle Singleton Key ==============================
2372
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2373
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2374
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2004
2375
  let Scheduler = _Scheduler;
2005
2376
  const getScheduler = () => Scheduler.get();
2006
2377
  if (hmrData) {
@@ -2008,11 +2379,9 @@ if (hmrData) {
2008
2379
  }
2009
2380
 
2010
2381
  function useFrame(callback, priorityOrOptions) {
2011
- const store = useStore();
2012
- const getRootId = React__namespace.useCallback(() => {
2013
- const state = store.getState();
2014
- return state.internal.rootId;
2015
- }, [store]);
2382
+ const store = React__namespace.useContext(context);
2383
+ const isInsideCanvas = store !== null;
2384
+ const scheduler = getScheduler();
2016
2385
  const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2017
2386
  id: priorityOrOptions.id,
2018
2387
  phase: priorityOrOptions.phase,
@@ -2032,55 +2401,71 @@ function useFrame(callback, priorityOrOptions) {
2032
2401
  const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2033
2402
  useIsomorphicLayoutEffect(() => {
2034
2403
  if (!callback) return;
2035
- const scheduler = getScheduler();
2036
- const rootId = getRootId();
2037
- const state = store.getState();
2038
- if (isLegacyPriority) {
2039
- state.internal.priority++;
2040
- let parentRoot = state.previousRoot;
2041
- while (parentRoot) {
2042
- const parentState = parentRoot.getState();
2043
- if (parentState?.internal) parentState.internal.priority++;
2044
- parentRoot = parentState?.previousRoot;
2045
- }
2046
- notifyDepreciated({
2047
- heading: "useFrame with numeric priority is deprecated",
2048
- 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 })',
2049
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2050
- });
2051
- }
2052
- const wrappedCallback = (frameState, delta) => {
2053
- const localState = store.getState();
2054
- const mergedState = {
2055
- ...localState,
2056
- time: frameState.time,
2057
- delta: frameState.delta,
2058
- elapsed: frameState.elapsed,
2059
- frame: frameState.frame
2060
- };
2061
- callbackRef.current?.(mergedState, delta);
2062
- };
2063
- const unregister = scheduler.register(wrappedCallback, {
2064
- id,
2065
- rootId,
2066
- ...options
2067
- });
2068
- return () => {
2069
- unregister();
2404
+ if (isInsideCanvas) {
2405
+ const state = store.getState();
2406
+ const rootId = state.internal.rootId;
2070
2407
  if (isLegacyPriority) {
2071
- const currentState = store.getState();
2072
- if (currentState.internal) {
2073
- currentState.internal.priority--;
2074
- let parentRoot = currentState.previousRoot;
2075
- while (parentRoot) {
2076
- const parentState = parentRoot.getState();
2077
- if (parentState?.internal) parentState.internal.priority--;
2078
- parentRoot = parentState?.previousRoot;
2408
+ state.internal.priority++;
2409
+ let parentRoot = state.previousRoot;
2410
+ while (parentRoot) {
2411
+ const parentState = parentRoot.getState();
2412
+ if (parentState?.internal) parentState.internal.priority++;
2413
+ parentRoot = parentState?.previousRoot;
2414
+ }
2415
+ notifyDepreciated({
2416
+ heading: "useFrame with numeric priority is deprecated",
2417
+ 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 })',
2418
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2419
+ });
2420
+ }
2421
+ const wrappedCallback = (frameState, delta) => {
2422
+ const localState = store.getState();
2423
+ const mergedState = {
2424
+ ...localState,
2425
+ time: frameState.time,
2426
+ delta: frameState.delta,
2427
+ elapsed: frameState.elapsed,
2428
+ frame: frameState.frame
2429
+ };
2430
+ callbackRef.current?.(mergedState, delta);
2431
+ };
2432
+ const unregister = scheduler.register(wrappedCallback, {
2433
+ id,
2434
+ rootId,
2435
+ ...options
2436
+ });
2437
+ return () => {
2438
+ unregister();
2439
+ if (isLegacyPriority) {
2440
+ const currentState = store.getState();
2441
+ if (currentState.internal) {
2442
+ currentState.internal.priority--;
2443
+ let parentRoot = currentState.previousRoot;
2444
+ while (parentRoot) {
2445
+ const parentState = parentRoot.getState();
2446
+ if (parentState?.internal) parentState.internal.priority--;
2447
+ parentRoot = parentState?.previousRoot;
2448
+ }
2079
2449
  }
2080
2450
  }
2451
+ };
2452
+ } else {
2453
+ const registerOutside = () => {
2454
+ return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2455
+ };
2456
+ if (scheduler.independent || scheduler.isReady) {
2457
+ return registerOutside();
2081
2458
  }
2082
- };
2083
- }, [store, id, optionsKey, isLegacyPriority]);
2459
+ let unregisterJob = null;
2460
+ const unsubReady = scheduler.onRootReady(() => {
2461
+ unregisterJob = registerOutside();
2462
+ });
2463
+ return () => {
2464
+ unsubReady();
2465
+ unregisterJob?.();
2466
+ };
2467
+ }
2468
+ }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2084
2469
  const isPaused = React__namespace.useSyncExternalStore(
2085
2470
  // Subscribe function
2086
2471
  React__namespace.useCallback(
@@ -2095,7 +2480,7 @@ function useFrame(callback, priorityOrOptions) {
2095
2480
  React__namespace.useCallback(() => false, [])
2096
2481
  );
2097
2482
  const controls = React__namespace.useMemo(() => {
2098
- const scheduler = getScheduler();
2483
+ const scheduler2 = getScheduler();
2099
2484
  return {
2100
2485
  /** The job's unique ID */
2101
2486
  id,
@@ -2103,7 +2488,7 @@ function useFrame(callback, priorityOrOptions) {
2103
2488
  * Access to the global scheduler for frame loop control.
2104
2489
  * Use for controlling the entire frame loop, adding phases, etc.
2105
2490
  */
2106
- scheduler,
2491
+ scheduler: scheduler2,
2107
2492
  /**
2108
2493
  * Manually step this job only.
2109
2494
  * Bypasses FPS limiting - always runs.
@@ -2351,6 +2736,18 @@ function useTextures() {
2351
2736
  }, [store]);
2352
2737
  }
2353
2738
 
2739
+ function useRenderTarget(width, height, options) {
2740
+ const isLegacy = useThree((s) => s.isLegacy);
2741
+ const size = useThree((s) => s.size);
2742
+ return React.useMemo(() => {
2743
+ const w = width ?? size.width;
2744
+ const h = height ?? size.height;
2745
+ {
2746
+ return isLegacy ? new three.WebGLRenderTarget(w, h, options) : new webgpu.RenderTarget(w, h, options);
2747
+ }
2748
+ }, [width, height, size.width, size.height, options, isLegacy]);
2749
+ }
2750
+
2354
2751
  function useStore() {
2355
2752
  const store = React.useContext(context);
2356
2753
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
@@ -2402,24 +2799,14 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
2402
2799
  getScheduler().step(timestamp);
2403
2800
  }
2404
2801
 
2405
- const version = "10.0.0-alpha.0";
2802
+ const version = "10.0.0-alpha.2";
2406
2803
  const packageData = {
2407
2804
  version: version};
2408
2805
 
2409
2806
  function Xb(Tt) {
2410
2807
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
2411
2808
  }
2412
- var Rm = { exports: {} }, Og = { exports: {} };
2413
- /**
2414
- * @license React
2415
- * react-reconciler.production.js
2416
- *
2417
- * Copyright (c) Meta Platforms, Inc. and affiliates.
2418
- *
2419
- * This source code is licensed under the MIT license found in the
2420
- * LICENSE file in the root directory of this source tree.
2421
- */
2422
- var _b;
2809
+ var Rm = { exports: {} }, Og = { exports: {} }, _b;
2423
2810
  function Kb() {
2424
2811
  return _b || (_b = 1, (function(Tt) {
2425
2812
  Tt.exports = function(m) {
@@ -3491,7 +3878,6 @@ Error generating stack: ` + l.message + `
3491
3878
  if (J === cl || J === jc) throw J;
3492
3879
  var Ge = Yn(29, J, null, P.mode);
3493
3880
  return Ge.lanes = H, Ge.return = P, Ge;
3494
- } finally {
3495
3881
  }
3496
3882
  };
3497
3883
  }
@@ -4145,7 +4531,6 @@ Error generating stack: ` + l.message + `
4145
4531
  var h = r.lastRenderedState, y = d(h, a);
4146
4532
  if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
4147
4533
  } catch {
4148
- } finally {
4149
4534
  }
4150
4535
  if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
4151
4536
  }
@@ -6566,10 +6951,7 @@ Error generating stack: ` + l.message + `
6566
6951
  function vr(t, r) {
6567
6952
  Sf(t, r), (t = t.alternate) && Sf(t, r);
6568
6953
  }
6569
- 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");
6570
- var gc = Symbol.for("react.activity");
6571
- var $r = Symbol.for("react.memo_cache_sentinel");
6572
- 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;
6954
+ 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;
6573
6955
  m.cloneMutableInstance;
6574
6956
  var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
6575
6957
  m.cloneMutableTextInstance;
@@ -6938,17 +7320,7 @@ No matching component was found for:
6938
7320
  }, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
6939
7321
  })(Og)), Og.exports;
6940
7322
  }
6941
- var Mg = { exports: {} };
6942
- /**
6943
- * @license React
6944
- * react-reconciler.development.js
6945
- *
6946
- * Copyright (c) Meta Platforms, Inc. and affiliates.
6947
- *
6948
- * This source code is licensed under the MIT license found in the
6949
- * LICENSE file in the root directory of this source tree.
6950
- */
6951
- var Rb;
7323
+ var Mg = { exports: {} }, Rb;
6952
7324
  function e0() {
6953
7325
  return Rb || (Rb = 1, (function(Tt) {
6954
7326
  process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
@@ -12715,10 +13087,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
12715
13087
  function Ic() {
12716
13088
  return di;
12717
13089
  }
12718
- 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");
12719
- var Ds = Symbol.for("react.activity");
12720
- var Bh = Symbol.for("react.memo_cache_sentinel");
12721
- 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;
13090
+ 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;
12722
13091
  m.cloneMutableInstance;
12723
13092
  var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
12724
13093
  m.cloneMutableTextInstance;
@@ -13686,15 +14055,6 @@ function n0() {
13686
14055
  var t0 = n0();
13687
14056
  const r0 = Xb(t0);
13688
14057
 
13689
- /**
13690
- * @license React
13691
- * react-reconciler-constants.production.js
13692
- *
13693
- * Copyright (c) Meta Platforms, Inc. and affiliates.
13694
- *
13695
- * This source code is licensed under the MIT license found in the
13696
- * LICENSE file in the root directory of this source tree.
13697
- */
13698
14058
  const t = 1, o = 8, r = 32, e = 2;
13699
14059
 
13700
14060
  function createReconciler(config) {
@@ -13703,7 +14063,8 @@ function createReconciler(config) {
13703
14063
  return reconciler2;
13704
14064
  }
13705
14065
  const NoEventPriority = 0;
13706
- const catalogue = {};
14066
+ const R3F_CATALOGUE = Symbol.for("@react-three/fiber.catalogue");
14067
+ const catalogue = globalThis[R3F_CATALOGUE] ?? (globalThis[R3F_CATALOGUE] = {});
13707
14068
  const PREFIX_REGEX = /^three(?=[A-Z])/;
13708
14069
  const toPascalCase = (type) => `${type[0].toUpperCase()}${type.slice(1)}`;
13709
14070
  let i = 0;
@@ -13720,10 +14081,11 @@ function extend(objects) {
13720
14081
  function validateInstance(type, props) {
13721
14082
  const name = toPascalCase(type);
13722
14083
  const target = catalogue[name];
13723
- if (type !== "primitive" && !target)
14084
+ if (type !== "primitive" && !target) {
13724
14085
  throw new Error(
13725
14086
  `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`
13726
14087
  );
14088
+ }
13727
14089
  if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
13728
14090
  if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
13729
14091
  }
@@ -14174,14 +14536,14 @@ function createRoot(canvas) {
14174
14536
  if (!prevRoot) _roots.set(canvas, { fiber, store });
14175
14537
  let onCreated;
14176
14538
  let lastCamera;
14177
- let lastConfiguredProps = {};
14539
+ const lastConfiguredProps = {};
14178
14540
  let configured = false;
14179
14541
  let pending = null;
14180
14542
  return {
14181
14543
  async configure(props = {}) {
14182
14544
  let resolve;
14183
14545
  pending = new Promise((_resolve) => resolve = _resolve);
14184
- let {
14546
+ const {
14185
14547
  gl: glConfig,
14186
14548
  renderer: rendererConfig,
14187
14549
  size: propsSize,
@@ -14201,9 +14563,12 @@ function createRoot(canvas) {
14201
14563
  camera: cameraOptions,
14202
14564
  onPointerMissed,
14203
14565
  onDragOverMissed,
14204
- onDropMissed
14566
+ onDropMissed,
14567
+ autoUpdateFrustum = true,
14568
+ occlusion = false,
14569
+ _sizeProps
14205
14570
  } = props;
14206
- let state = store.getState();
14571
+ const state = store.getState();
14207
14572
  const defaultGLProps = {
14208
14573
  canvas,
14209
14574
  powerPreference: "high-performance",
@@ -14231,7 +14596,9 @@ function createRoot(canvas) {
14231
14596
  state.set({ isLegacy: true, gl: renderer, renderer });
14232
14597
  } else if (!wantsGL && !state.internal.actualRenderer) {
14233
14598
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14234
- await renderer.init();
14599
+ if (!renderer.hasInitialized?.()) {
14600
+ await renderer.init();
14601
+ }
14235
14602
  const backend = renderer.backend;
14236
14603
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14237
14604
  state.internal.actualRenderer = renderer;
@@ -14241,8 +14608,9 @@ function createRoot(canvas) {
14241
14608
  if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
14242
14609
  const { params, ...options } = raycastOptions || {};
14243
14610
  if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
14244
- if (!is.equ(params, raycaster.params, shallowLoose))
14611
+ if (!is.equ(params, raycaster.params, shallowLoose)) {
14245
14612
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14613
+ }
14246
14614
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14247
14615
  lastCamera = cameraOptions;
14248
14616
  const isCamera = cameraOptions?.isCamera;
@@ -14279,6 +14647,8 @@ function createRoot(canvas) {
14279
14647
  rootScene: scene,
14280
14648
  internal: { ...prev.internal, container: scene }
14281
14649
  }));
14650
+ const camera = state.camera;
14651
+ if (camera && !camera.parent) scene.add(camera);
14282
14652
  }
14283
14653
  if (events && !state.events.handlers) {
14284
14654
  state.set({ events: events(store) });
@@ -14291,9 +14661,14 @@ function createRoot(canvas) {
14291
14661
  wasEnabled = enabled;
14292
14662
  });
14293
14663
  }
14664
+ if (_sizeProps !== void 0) {
14665
+ state.set({ _sizeProps });
14666
+ }
14294
14667
  const size = computeInitialSize(canvas, propsSize);
14295
- if (!is.equ(size, state.size, shallowLoose)) {
14668
+ if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14669
+ const wasImperative = state._sizeImperative;
14296
14670
  state.setSize(size.width, size.height, size.top, size.left);
14671
+ if (!wasImperative) state.set({ _sizeImperative: false });
14297
14672
  }
14298
14673
  if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
14299
14674
  state.setDpr(dpr);
@@ -14306,6 +14681,10 @@ function createRoot(canvas) {
14306
14681
  if (!state.onPointerMissed) state.set({ onPointerMissed });
14307
14682
  if (!state.onDragOverMissed) state.set({ onDragOverMissed });
14308
14683
  if (!state.onDropMissed) state.set({ onDropMissed });
14684
+ if (state.autoUpdateFrustum !== autoUpdateFrustum) state.set({ autoUpdateFrustum });
14685
+ if (occlusion && !state.internal.occlusionEnabled) {
14686
+ enableOcclusion(store);
14687
+ }
14309
14688
  if (performance && !is.equ(performance, lastConfiguredProps.performance, shallowLoose)) {
14310
14689
  state.set((state2) => ({ performance: { ...state2.performance, ...performance } }));
14311
14690
  lastConfiguredProps.performance = performance;
@@ -14359,20 +14738,22 @@ function createRoot(canvas) {
14359
14738
  } else if (is.obj(shadows)) {
14360
14739
  Object.assign(renderer.shadowMap, shadows);
14361
14740
  }
14362
- if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
14741
+ if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
14363
14742
  renderer.shadowMap.needsUpdate = true;
14743
+ }
14364
14744
  }
14365
14745
  {
14366
14746
  const legacyChanged = legacy !== lastConfiguredProps.legacy;
14367
14747
  const linearChanged = linear !== lastConfiguredProps.linear;
14368
14748
  const flatChanged = flat !== lastConfiguredProps.flat;
14369
14749
  if (legacyChanged) {
14370
- if (legacy)
14750
+ if (legacy) {
14371
14751
  notifyDepreciated({
14372
14752
  heading: "Legacy Color Management",
14373
14753
  body: "Legacy color management is deprecated and will be removed in a future version.",
14374
14754
  link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
14375
14755
  });
14756
+ }
14376
14757
  }
14377
14758
  if (legacyChanged) {
14378
14759
  webgpu.ColorManagement.enabled = !legacy;
@@ -14394,8 +14775,9 @@ function createRoot(canvas) {
14394
14775
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14395
14776
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14396
14777
  }
14397
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose))
14778
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14398
14779
  applyProps(renderer, glConfig);
14780
+ }
14399
14781
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14400
14782
  const currentRenderer = state.renderer;
14401
14783
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
@@ -14406,7 +14788,37 @@ function createRoot(canvas) {
14406
14788
  const rootId = state.internal.rootId;
14407
14789
  if (!rootId) {
14408
14790
  const newRootId = scheduler.generateRootId();
14409
- const unregisterRoot = scheduler.registerRoot(newRootId, () => store.getState());
14791
+ const unregisterRoot = scheduler.registerRoot(newRootId, {
14792
+ getState: () => store.getState(),
14793
+ onError: (err) => store.getState().setError(err)
14794
+ });
14795
+ const unregisterFrustum = scheduler.register(
14796
+ () => {
14797
+ const state2 = store.getState();
14798
+ if (state2.autoUpdateFrustum && state2.camera) {
14799
+ updateFrustum(state2.camera, state2.frustum);
14800
+ }
14801
+ },
14802
+ {
14803
+ id: `${newRootId}_frustum`,
14804
+ rootId: newRootId,
14805
+ phase: "preRender",
14806
+ system: true
14807
+ }
14808
+ );
14809
+ const unregisterVisibility = scheduler.register(
14810
+ () => {
14811
+ const state2 = store.getState();
14812
+ checkVisibility(state2);
14813
+ },
14814
+ {
14815
+ id: `${newRootId}_visibility`,
14816
+ rootId: newRootId,
14817
+ phase: "preRender",
14818
+ system: true,
14819
+ after: `${newRootId}_frustum`
14820
+ }
14821
+ );
14410
14822
  const unregisterRender = scheduler.register(
14411
14823
  () => {
14412
14824
  const state2 = store.getState();
@@ -14434,6 +14846,8 @@ function createRoot(canvas) {
14434
14846
  rootId: newRootId,
14435
14847
  unregisterRoot: () => {
14436
14848
  unregisterRoot();
14849
+ unregisterFrustum();
14850
+ unregisterVisibility();
14437
14851
  unregisterRender();
14438
14852
  },
14439
14853
  scheduler
@@ -14491,6 +14905,7 @@ function unmountComponentAtNode(canvas, callback) {
14491
14905
  const unregisterRoot = state.internal.unregisterRoot;
14492
14906
  if (unregisterRoot) unregisterRoot();
14493
14907
  state.events.disconnect?.();
14908
+ cleanupHelperGroup(root.store);
14494
14909
  renderer?.renderLists?.dispose?.();
14495
14910
  renderer?.forceContextLoss?.();
14496
14911
  if (renderer?.xr) state.xr.disconnect();
@@ -14630,6 +15045,9 @@ function CanvasImpl({
14630
15045
  onDragOverMissed,
14631
15046
  onDropMissed,
14632
15047
  onCreated,
15048
+ hmr,
15049
+ width,
15050
+ height,
14633
15051
  ...props
14634
15052
  }) {
14635
15053
  React__namespace.useMemo(() => extend(THREE), []);
@@ -14650,7 +15068,16 @@ function CanvasImpl({
14650
15068
  };
14651
15069
  }, [resize, hasInitialSizeRef.current]);
14652
15070
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
14653
- if (!hasInitialSizeRef.current && containerRect.width > 0 && containerRect.height > 0) {
15071
+ const effectiveSize = React__namespace.useMemo(
15072
+ () => ({
15073
+ width: width ?? containerRect.width,
15074
+ height: height ?? containerRect.height,
15075
+ top: containerRect.top,
15076
+ left: containerRect.left
15077
+ }),
15078
+ [width, height, containerRect]
15079
+ );
15080
+ if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
14654
15081
  hasInitialSizeRef.current = true;
14655
15082
  }
14656
15083
  const canvasRef = React__namespace.useRef(null);
@@ -14669,8 +15096,23 @@ function CanvasImpl({
14669
15096
  useIsomorphicLayoutEffect(() => {
14670
15097
  effectActiveRef.current = true;
14671
15098
  const canvas = canvasRef.current;
14672
- if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
14673
- if (!root.current) root.current = createRoot(canvas);
15099
+ if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
15100
+ if (!root.current) {
15101
+ root.current = createRoot(canvas);
15102
+ notifyAlpha({
15103
+ message: "React Three Fiber v10 is in ALPHA - expect breaking changes",
15104
+ link: "https://github.com/pmndrs/react-three-fiber/discussions"
15105
+ });
15106
+ const rootEntry = _roots.get(canvas);
15107
+ if (rootEntry?.store) {
15108
+ if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
15109
+ unsubscribeErrorRef.current = rootEntry.store.subscribe((state) => {
15110
+ if (state.error && effectActiveRef.current) {
15111
+ setError(state.error);
15112
+ }
15113
+ });
15114
+ }
15115
+ }
14674
15116
  async function run() {
14675
15117
  if (!effectActiveRef.current || !root.current) return;
14676
15118
  await root.current.configure({
@@ -14688,7 +15130,9 @@ function CanvasImpl({
14688
15130
  performance,
14689
15131
  raycaster,
14690
15132
  camera,
14691
- size: containerRect,
15133
+ size: effectiveSize,
15134
+ // Store size props for reset functionality
15135
+ _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
14692
15136
  // Pass mutable reference to onPointerMissed so it's free to update
14693
15137
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
14694
15138
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -14711,15 +15155,9 @@ function CanvasImpl({
14711
15155
  }
14712
15156
  });
14713
15157
  if (!effectActiveRef.current || !root.current) return;
14714
- const store = root.current.render(
15158
+ root.current.render(
14715
15159
  /* @__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 }) }) })
14716
15160
  );
14717
- if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
14718
- unsubscribeErrorRef.current = store.subscribe((state) => {
14719
- if (state.error && effectActiveRef.current) {
14720
- setError(state.error);
14721
- }
14722
- });
14723
15161
  }
14724
15162
  run();
14725
15163
  }
@@ -14740,6 +15178,33 @@ function CanvasImpl({
14740
15178
  };
14741
15179
  }
14742
15180
  }, []);
15181
+ React__namespace.useEffect(() => {
15182
+ if (hmr === false) return;
15183
+ const canvas = canvasRef.current;
15184
+ if (!canvas) return;
15185
+ const handleHMR = () => {
15186
+ const rootEntry = _roots.get(canvas);
15187
+ if (rootEntry?.store) {
15188
+ rootEntry.store.setState((state) => ({
15189
+ nodes: {},
15190
+ uniforms: {},
15191
+ _hmrVersion: state._hmrVersion + 1
15192
+ }));
15193
+ }
15194
+ };
15195
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15196
+ const hot = undefined;
15197
+ hot.on("vite:afterUpdate", handleHMR);
15198
+ return () => hot.dispose?.(() => {
15199
+ });
15200
+ }
15201
+ if (typeof module !== "undefined" && module.hot) {
15202
+ const hot = module.hot;
15203
+ hot.addStatusHandler((status) => {
15204
+ if (status === "idle") handleHMR();
15205
+ });
15206
+ }
15207
+ }, [hmr]);
14743
15208
  const pointerEvents = eventSource ? "none" : "auto";
14744
15209
  return /* @__PURE__ */ jsxRuntime.jsx(
14745
15210
  "div",
@@ -14819,6 +15284,7 @@ exports.removeInteractivity = removeInteractivity;
14819
15284
  exports.resolve = resolve;
14820
15285
  exports.unmountComponentAtNode = unmountComponentAtNode;
14821
15286
  exports.updateCamera = updateCamera;
15287
+ exports.updateFrustum = updateFrustum;
14822
15288
  exports.useBridge = useBridge;
14823
15289
  exports.useFrame = useFrame;
14824
15290
  exports.useGraph = useGraph;
@@ -14826,6 +15292,7 @@ exports.useInstanceHandle = useInstanceHandle;
14826
15292
  exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
14827
15293
  exports.useLoader = useLoader;
14828
15294
  exports.useMutableCallback = useMutableCallback;
15295
+ exports.useRenderTarget = useRenderTarget;
14829
15296
  exports.useStore = useStore;
14830
15297
  exports.useTexture = useTexture;
14831
15298
  exports.useTextures = useTextures;