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

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
@@ -51,6 +51,8 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
51
51
  Inspector: Inspector_js.Inspector,
52
52
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
53
53
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
54
+ RenderTargetCompat: webgpu.RenderTarget,
55
+ WebGLRenderTarget: three.WebGLRenderTarget,
54
56
  WebGLRenderer: three.WebGLRenderer
55
57
  }, [webgpu__namespace]);
56
58
 
@@ -159,6 +161,13 @@ function updateCamera(camera, size) {
159
161
  }
160
162
  camera.updateProjectionMatrix();
161
163
  }
164
+ const frustumMatrix = new webgpu.Matrix4();
165
+ function updateFrustum(camera, frustum) {
166
+ const target = frustum ?? new webgpu.Frustum();
167
+ frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
168
+ target.setFromProjectionMatrix(frustumMatrix);
169
+ return target;
170
+ }
162
171
 
163
172
  const REACT_INTERNAL_PROPS = ["children", "key", "ref"];
164
173
  function findInitialRoot(instance) {
@@ -239,6 +248,205 @@ function invalidateInstance(instance) {
239
248
  if (state && state.internal.frames === 0) state.invalidate();
240
249
  }
241
250
 
251
+ const tempFrustum = new webgpu.Frustum();
252
+ let hasWarnedWebGL = false;
253
+ let tslModule = null;
254
+ async function loadTSL() {
255
+ if (tslModule) return tslModule;
256
+ try {
257
+ const tsl = await import('three/tsl');
258
+ tslModule = { uniform: tsl.uniform, nodeObject: tsl.nodeObject };
259
+ return tslModule;
260
+ } catch {
261
+ return null;
262
+ }
263
+ }
264
+ function createOcclusionObserverNode(store, uniform) {
265
+ const node = new webgpu.Node("float");
266
+ node.updateType = webgpu.NodeUpdateType.OBJECT;
267
+ node.update = function(frame) {
268
+ const { internal } = store.getState();
269
+ const registry = internal.visibilityRegistry;
270
+ const cache = internal.occlusionCache;
271
+ for (const entry of registry.values()) {
272
+ const { object, handlers } = entry;
273
+ if (handlers.onOccluded || handlers.onVisible) {
274
+ const isOccluded = frame.renderer.isOccluded(object);
275
+ cache.set(object, isOccluded);
276
+ }
277
+ }
278
+ };
279
+ node.setup = function() {
280
+ return uniform(0);
281
+ };
282
+ return node;
283
+ }
284
+ let occlusionSetupPromise = null;
285
+ function enableOcclusion(store) {
286
+ const state = store.getState();
287
+ const { internal, renderer, rootScene } = state;
288
+ if (internal.occlusionEnabled || occlusionSetupPromise) return;
289
+ const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
290
+ if (!hasOcclusionSupport) {
291
+ if (!hasWarnedWebGL) {
292
+ console.warn(
293
+ "[R3F] Warning: onOccluded/onVisible occlusion queries require WebGPU renderer. Occlusion events will not fire on WebGL."
294
+ );
295
+ hasWarnedWebGL = true;
296
+ }
297
+ return;
298
+ }
299
+ occlusionSetupPromise = setupOcclusion(store);
300
+ }
301
+ async function setupOcclusion(store) {
302
+ const state = store.getState();
303
+ const { internal, rootScene, set } = state;
304
+ const tsl = await loadTSL();
305
+ if (!tsl) {
306
+ console.warn("[R3F] Warning: TSL module not available. Occlusion queries disabled.");
307
+ occlusionSetupPromise = null;
308
+ return;
309
+ }
310
+ const { uniform, nodeObject } = tsl;
311
+ let helperGroup = internal.helperGroup;
312
+ if (!helperGroup) {
313
+ helperGroup = new webgpu.Group();
314
+ helperGroup.name = "__r3fInternal";
315
+ helperGroup.__r3fInternal = true;
316
+ rootScene.add(helperGroup);
317
+ }
318
+ const geometry = new webgpu.BoxGeometry(1, 1, 1);
319
+ const material = new webgpu.MeshBasicNodeMaterial({
320
+ transparent: true,
321
+ opacity: 0
322
+ });
323
+ const observerNode = nodeObject(createOcclusionObserverNode(store, uniform));
324
+ material.colorNode = observerNode;
325
+ material.needsUpdate = true;
326
+ const mesh = new webgpu.Mesh(geometry, material);
327
+ mesh.name = "__r3fOcclusionObserver";
328
+ mesh.scale.setScalar(1e-4);
329
+ mesh.frustumCulled = false;
330
+ mesh.__r3fInternal = true;
331
+ helperGroup.add(mesh);
332
+ set((state2) => ({
333
+ internal: {
334
+ ...state2.internal,
335
+ helperGroup,
336
+ occlusionObserver: mesh,
337
+ occlusionEnabled: true
338
+ }
339
+ }));
340
+ occlusionSetupPromise = null;
341
+ }
342
+ function disableOcclusion(store) {
343
+ const { internal, set } = store.getState();
344
+ if (!internal.occlusionEnabled) return;
345
+ if (internal.occlusionObserver) {
346
+ internal.occlusionObserver.removeFromParent();
347
+ internal.occlusionObserver.geometry.dispose();
348
+ internal.occlusionObserver.material.dispose();
349
+ }
350
+ internal.occlusionCache.clear();
351
+ set((state) => ({
352
+ internal: {
353
+ ...state.internal,
354
+ occlusionObserver: null,
355
+ occlusionEnabled: false
356
+ }
357
+ }));
358
+ }
359
+ function cleanupHelperGroup(store) {
360
+ const { internal, set } = store.getState();
361
+ disableOcclusion(store);
362
+ if (internal.helperGroup) {
363
+ internal.helperGroup.removeFromParent();
364
+ set((state) => ({
365
+ internal: {
366
+ ...state.internal,
367
+ helperGroup: null
368
+ }
369
+ }));
370
+ }
371
+ }
372
+ function registerVisibility(store, object, handlers) {
373
+ const { internal } = store.getState();
374
+ const registry = internal.visibilityRegistry;
375
+ const entry = {
376
+ object,
377
+ handlers,
378
+ lastFramedState: null,
379
+ lastOccludedState: null,
380
+ lastVisibleState: null
381
+ };
382
+ registry.set(object.uuid, entry);
383
+ if (handlers.onOccluded || handlers.onVisible) {
384
+ object.occlusionTest = true;
385
+ if (!internal.occlusionEnabled) {
386
+ enableOcclusion(store);
387
+ }
388
+ }
389
+ }
390
+ function unregisterVisibility(store, object) {
391
+ const { internal } = store.getState();
392
+ internal.visibilityRegistry.delete(object.uuid);
393
+ internal.occlusionCache.delete(object);
394
+ }
395
+ function checkVisibility(state) {
396
+ const { internal, camera } = state;
397
+ const registry = internal.visibilityRegistry;
398
+ if (registry.size === 0) return;
399
+ updateFrustum(camera, tempFrustum);
400
+ for (const entry of registry.values()) {
401
+ const { object, handlers, lastFramedState, lastOccludedState, lastVisibleState } = entry;
402
+ let inFrustum = null;
403
+ const computeFrustum = () => {
404
+ if (inFrustum === null) {
405
+ if (object.geometry?.boundingSphere === null) {
406
+ object.geometry?.computeBoundingSphere();
407
+ }
408
+ inFrustum = tempFrustum.intersectsObject(object);
409
+ }
410
+ return inFrustum;
411
+ };
412
+ if (handlers.onFramed) {
413
+ const currentInFrustum = computeFrustum();
414
+ if (currentInFrustum !== lastFramedState) {
415
+ entry.lastFramedState = currentInFrustum;
416
+ handlers.onFramed(currentInFrustum);
417
+ }
418
+ }
419
+ let currentOcclusion = null;
420
+ if (handlers.onOccluded && internal.occlusionEnabled) {
421
+ currentOcclusion = internal.occlusionCache.get(object) ?? null;
422
+ if (currentOcclusion !== null && currentOcclusion !== lastOccludedState) {
423
+ entry.lastOccludedState = currentOcclusion;
424
+ handlers.onOccluded(currentOcclusion);
425
+ }
426
+ }
427
+ if (handlers.onVisible) {
428
+ const currentInFrustum = computeFrustum();
429
+ if (!handlers.onFramed && currentInFrustum !== lastFramedState) {
430
+ entry.lastFramedState = currentInFrustum;
431
+ }
432
+ let isOccluded = currentOcclusion;
433
+ if (isOccluded === null && internal.occlusionEnabled) {
434
+ isOccluded = internal.occlusionCache.get(object) ?? null;
435
+ }
436
+ if (isOccluded === null) isOccluded = false;
437
+ const isVisible = currentInFrustum && !isOccluded && object.visible;
438
+ if (isVisible !== lastVisibleState) {
439
+ entry.lastVisibleState = isVisible;
440
+ handlers.onVisible(isVisible);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ function hasVisibilityHandlers(handlers) {
446
+ if (!handlers) return false;
447
+ return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
448
+ }
449
+
242
450
  const RESERVED_PROPS = [
243
451
  "children",
244
452
  "key",
@@ -253,6 +461,7 @@ const RESERVED_PROPS = [
253
461
  "dispose"
254
462
  ];
255
463
  const EVENT_REGEX = /^on(Pointer|Drag|Drop|Click|DoubleClick|ContextMenu|Wheel)/;
464
+ const VISIBILITY_EVENT_REGEX = /^on(Framed|Occluded|Visible)$/;
256
465
  const INDEX_REGEX = /-\d+$/;
257
466
  const MEMOIZED_PROTOTYPES = /* @__PURE__ */ new Map();
258
467
  const colorMaps = ["map", "emissiveMap", "sheenColorMap", "specularColorMap", "envMap"];
@@ -347,6 +556,12 @@ function applyProps(object, props) {
347
556
  instance.eventCount = Object.keys(instance.handlers).length;
348
557
  continue;
349
558
  }
559
+ if (instance && VISIBILITY_EVENT_REGEX.test(prop)) {
560
+ if (typeof value === "function") instance.handlers[prop] = value;
561
+ else delete instance.handlers[prop];
562
+ instance.eventCount = Object.keys(instance.handlers).length;
563
+ continue;
564
+ }
350
565
  if (value === void 0) continue;
351
566
  let { root, key, target } = resolve(object, prop);
352
567
  if (target === void 0 && (typeof root !== "object" || root === null)) {
@@ -383,6 +598,17 @@ function applyProps(object, props) {
383
598
  if (instance.eventCount && object2.raycast !== null) {
384
599
  rootState.internal.interaction.push(object2);
385
600
  }
601
+ const root = findInitialRoot(instance);
602
+ const visibilityHandlers = {
603
+ onFramed: instance.handlers.onFramed,
604
+ onOccluded: instance.handlers.onOccluded,
605
+ onVisible: instance.handlers.onVisible
606
+ };
607
+ if (hasVisibilityHandlers(visibilityHandlers)) {
608
+ registerVisibility(root, object2, visibilityHandlers);
609
+ } else {
610
+ unregisterVisibility(root, object2);
611
+ }
386
612
  }
387
613
  if (instance && instance.props.attach === void 0) {
388
614
  if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
@@ -417,6 +643,7 @@ function removeInteractivity(store, object) {
417
643
  internal.capturedMap.forEach((captures, pointerId) => {
418
644
  releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
419
645
  });
646
+ unregisterVisibility(store, object);
420
647
  }
421
648
  function createEvents(store) {
422
649
  function calculateDistance(event) {
@@ -799,8 +1026,21 @@ function formatLocation(url, line) {
799
1026
  const file = clean.split("/").pop() ?? clean;
800
1027
  return `${file}:${line}`;
801
1028
  }
1029
+ function notifyAlpha({ message, link }) {
1030
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
1031
+ return;
1032
+ }
1033
+ if (shownNotices.has(message)) return;
1034
+ shownNotices.add(message);
1035
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
1036
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
1037
+ {
1038
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
1039
+ }
1040
+ }
802
1041
 
803
- const context = /* @__PURE__ */ React__namespace.createContext(null);
1042
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1043
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
804
1044
  const createStore = (invalidate, advance) => {
805
1045
  const rootStore = traditional.createWithEqualityFn((set, get) => {
806
1046
  const position = new webgpu.Vector3();
@@ -831,6 +1071,8 @@ const createStore = (invalidate, advance) => {
831
1071
  gl: null,
832
1072
  renderer: null,
833
1073
  camera: null,
1074
+ frustum: new webgpu.Frustum(),
1075
+ autoUpdateFrustum: true,
834
1076
  raycaster: null,
835
1077
  events: { priority: 1, enabled: true, connected: false },
836
1078
  scene: null,
@@ -912,6 +1154,13 @@ const createStore = (invalidate, advance) => {
912
1154
  initialHits: [],
913
1155
  capturedMap: /* @__PURE__ */ new Map(),
914
1156
  lastEvent: React__namespace.createRef(),
1157
+ // Visibility tracking (onFramed, onOccluded, onVisible)
1158
+ visibilityRegistry: /* @__PURE__ */ new Map(),
1159
+ // Occlusion system (WebGPU only)
1160
+ occlusionEnabled: false,
1161
+ occlusionObserver: null,
1162
+ occlusionCache: /* @__PURE__ */ new Map(),
1163
+ helperGroup: null,
915
1164
  // Updates
916
1165
  active: false,
917
1166
  frames: 0,
@@ -997,7 +1246,15 @@ const createStore = (invalidate, advance) => {
997
1246
  }
998
1247
  if (camera !== oldCamera) {
999
1248
  oldCamera = camera;
1249
+ const { rootScene } = rootStore.getState();
1250
+ if (camera && rootScene && !camera.parent) {
1251
+ rootScene.add(camera);
1252
+ }
1000
1253
  set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1254
+ const currentState = rootStore.getState();
1255
+ if (currentState.autoUpdateFrustum && camera) {
1256
+ updateFrustum(camera, currentState.frustum);
1257
+ }
1001
1258
  }
1002
1259
  });
1003
1260
  rootStore.subscribe((state2) => invalidate(state2));
@@ -1361,6 +1618,10 @@ const _Scheduler = class _Scheduler {
1361
1618
  __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1362
1619
  __publicField(this, "pendingFrames", 0);
1363
1620
  __publicField(this, "_frameloop", "always");
1621
+ //* Independent Mode & Error Handling State ================================
1622
+ __publicField(this, "_independent", false);
1623
+ __publicField(this, "errorHandler", null);
1624
+ __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1364
1625
  //* Core Loop Execution Methods ================================
1365
1626
  /**
1366
1627
  * Main RAF loop callback.
@@ -1383,6 +1644,12 @@ const _Scheduler = class _Scheduler {
1383
1644
  });
1384
1645
  this.phaseGraph = new PhaseGraph();
1385
1646
  }
1647
+ static get instance() {
1648
+ return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1649
+ }
1650
+ static set instance(value) {
1651
+ globalThis[_Scheduler.INSTANCE_KEY] = value;
1652
+ }
1386
1653
  /**
1387
1654
  * Get the global scheduler instance (creates if doesn't exist).
1388
1655
  * Uses HMR data to preserve instance across hot reloads.
@@ -1431,29 +1698,43 @@ const _Scheduler = class _Scheduler {
1431
1698
  get isRunning() {
1432
1699
  return this.loopState.running;
1433
1700
  }
1701
+ get isReady() {
1702
+ return this.roots.size > 0;
1703
+ }
1704
+ get independent() {
1705
+ return this._independent;
1706
+ }
1707
+ set independent(value) {
1708
+ this._independent = value;
1709
+ if (value) this.ensureDefaultRoot();
1710
+ }
1434
1711
  //* Root Management Methods ================================
1435
1712
  /**
1436
1713
  * Register a root (Canvas) with the scheduler.
1437
1714
  * The first root to register starts the RAF loop (if frameloop='always').
1438
1715
  * @param {string} id - Unique identifier for this root
1439
- * @param {() => RootState} getState - Function to get the root's current state
1716
+ * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1440
1717
  * @returns {() => void} Unsubscribe function to remove this root
1441
1718
  */
1442
- registerRoot(id, getState) {
1719
+ registerRoot(id, options = {}) {
1443
1720
  if (this.roots.has(id)) {
1444
1721
  console.warn(`[Scheduler] Root "${id}" already registered`);
1445
1722
  return () => this.unregisterRoot(id);
1446
1723
  }
1447
1724
  const entry = {
1448
1725
  id,
1449
- getState,
1726
+ getState: options.getState ?? (() => ({})),
1450
1727
  jobs: /* @__PURE__ */ new Map(),
1451
1728
  sortedJobs: [],
1452
1729
  needsRebuild: false
1453
1730
  };
1731
+ if (options.onError) {
1732
+ this.errorHandler = options.onError;
1733
+ }
1454
1734
  this.roots.set(id, entry);
1455
- if (this.roots.size === 1 && this._frameloop === "always") {
1456
- this.start();
1735
+ if (this.roots.size === 1) {
1736
+ this.notifyRootReady();
1737
+ if (this._frameloop === "always") this.start();
1457
1738
  }
1458
1739
  return () => this.unregisterRoot(id);
1459
1740
  }
@@ -1473,8 +1754,61 @@ const _Scheduler = class _Scheduler {
1473
1754
  this.roots.delete(id);
1474
1755
  if (this.roots.size === 0) {
1475
1756
  this.stop();
1757
+ this.errorHandler = null;
1758
+ }
1759
+ }
1760
+ /**
1761
+ * Subscribe to be notified when a root becomes available.
1762
+ * Fires immediately if a root already exists.
1763
+ * @param {() => void} callback - Function called when first root registers
1764
+ * @returns {() => void} Unsubscribe function
1765
+ */
1766
+ onRootReady(callback) {
1767
+ if (this.roots.size > 0) {
1768
+ callback();
1769
+ return () => {
1770
+ };
1771
+ }
1772
+ this.rootReadyCallbacks.add(callback);
1773
+ return () => this.rootReadyCallbacks.delete(callback);
1774
+ }
1775
+ /**
1776
+ * Notify all registered root-ready callbacks.
1777
+ * Called when the first root registers.
1778
+ * @returns {void}
1779
+ * @private
1780
+ */
1781
+ notifyRootReady() {
1782
+ for (const cb of this.rootReadyCallbacks) {
1783
+ try {
1784
+ cb();
1785
+ } catch (error) {
1786
+ console.error("[Scheduler] Error in root-ready callback:", error);
1787
+ }
1788
+ }
1789
+ this.rootReadyCallbacks.clear();
1790
+ }
1791
+ /**
1792
+ * Ensure a default root exists for independent mode.
1793
+ * Creates a minimal root with no state provider.
1794
+ * @returns {void}
1795
+ * @private
1796
+ */
1797
+ ensureDefaultRoot() {
1798
+ if (!this.roots.has("__default__")) {
1799
+ this.registerRoot("__default__");
1476
1800
  }
1477
1801
  }
1802
+ /**
1803
+ * Trigger error handling for job errors.
1804
+ * Uses the bound error handler if available, otherwise logs to console.
1805
+ * @param {Error} error - The error to handle
1806
+ * @returns {void}
1807
+ */
1808
+ triggerError(error) {
1809
+ if (this.errorHandler) this.errorHandler(error);
1810
+ else console.error("[Scheduler]", error);
1811
+ }
1478
1812
  //* Phase Management Methods ================================
1479
1813
  /**
1480
1814
  * Add a named phase to the scheduler's execution order.
@@ -1833,9 +2167,9 @@ const _Scheduler = class _Scheduler {
1833
2167
  const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
1834
2168
  const delta = deltaMs / 1e3;
1835
2169
  const elapsed = now - this.loopState.createdAt;
1836
- const rootState = root.getState();
2170
+ const providedState = root.getState?.() ?? {};
1837
2171
  const frameState = {
1838
- ...rootState,
2172
+ ...providedState,
1839
2173
  time: now,
1840
2174
  delta,
1841
2175
  elapsed,
@@ -1845,6 +2179,7 @@ const _Scheduler = class _Scheduler {
1845
2179
  job.callback(frameState, delta);
1846
2180
  } catch (error) {
1847
2181
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
2182
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1848
2183
  }
1849
2184
  }
1850
2185
  /**
@@ -1886,7 +2221,7 @@ const _Scheduler = class _Scheduler {
1886
2221
  /**
1887
2222
  * Execute all jobs for a single root in sorted order.
1888
2223
  * Rebuilds sorted job list if needed, then dispatches each job.
1889
- * Errors are caught and propagated to the root's error boundary.
2224
+ * Errors are caught and propagated via triggerError.
1890
2225
  * @param {RootEntry} root - The root entry to tick
1891
2226
  * @param {number} timestamp - RAF timestamp in milliseconds
1892
2227
  * @param {number} delta - Time since last frame in seconds
@@ -1898,10 +2233,9 @@ const _Scheduler = class _Scheduler {
1898
2233
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
1899
2234
  root.needsRebuild = false;
1900
2235
  }
1901
- const rootState = root.getState();
1902
- if (!rootState) return;
2236
+ const providedState = root.getState?.() ?? {};
1903
2237
  const frameState = {
1904
- ...rootState,
2238
+ ...providedState,
1905
2239
  time: timestamp,
1906
2240
  delta,
1907
2241
  elapsed: this.loopState.elapsedTime / 1e3,
@@ -1914,7 +2248,7 @@ const _Scheduler = class _Scheduler {
1914
2248
  job.callback(frameState, delta);
1915
2249
  } catch (error) {
1916
2250
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
1917
- rootState.setError(error instanceof Error ? error : new Error(String(error)));
2251
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1918
2252
  }
1919
2253
  }
1920
2254
  }
@@ -1999,8 +2333,11 @@ const _Scheduler = class _Scheduler {
1999
2333
  return /* @__PURE__ */ new Set([value]);
2000
2334
  }
2001
2335
  };
2002
- //* Static State & Methods (Singlton Usage) ================================
2003
- __publicField(_Scheduler, "instance", null);
2336
+ //* Static State & Methods (Singleton Usage) ================================
2337
+ //* Cross-Bundle Singleton Key ==============================
2338
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2339
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2340
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2004
2341
  let Scheduler = _Scheduler;
2005
2342
  const getScheduler = () => Scheduler.get();
2006
2343
  if (hmrData) {
@@ -2008,11 +2345,9 @@ if (hmrData) {
2008
2345
  }
2009
2346
 
2010
2347
  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]);
2348
+ const store = React__namespace.useContext(context);
2349
+ const isInsideCanvas = store !== null;
2350
+ const scheduler = getScheduler();
2016
2351
  const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2017
2352
  id: priorityOrOptions.id,
2018
2353
  phase: priorityOrOptions.phase,
@@ -2032,55 +2367,71 @@ function useFrame(callback, priorityOrOptions) {
2032
2367
  const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2033
2368
  useIsomorphicLayoutEffect(() => {
2034
2369
  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();
2370
+ if (isInsideCanvas) {
2371
+ const state = store.getState();
2372
+ const rootId = state.internal.rootId;
2070
2373
  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;
2374
+ state.internal.priority++;
2375
+ let parentRoot = state.previousRoot;
2376
+ while (parentRoot) {
2377
+ const parentState = parentRoot.getState();
2378
+ if (parentState?.internal) parentState.internal.priority++;
2379
+ parentRoot = parentState?.previousRoot;
2380
+ }
2381
+ notifyDepreciated({
2382
+ heading: "useFrame with numeric priority is deprecated",
2383
+ 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 })',
2384
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2385
+ });
2386
+ }
2387
+ const wrappedCallback = (frameState, delta) => {
2388
+ const localState = store.getState();
2389
+ const mergedState = {
2390
+ ...localState,
2391
+ time: frameState.time,
2392
+ delta: frameState.delta,
2393
+ elapsed: frameState.elapsed,
2394
+ frame: frameState.frame
2395
+ };
2396
+ callbackRef.current?.(mergedState, delta);
2397
+ };
2398
+ const unregister = scheduler.register(wrappedCallback, {
2399
+ id,
2400
+ rootId,
2401
+ ...options
2402
+ });
2403
+ return () => {
2404
+ unregister();
2405
+ if (isLegacyPriority) {
2406
+ const currentState = store.getState();
2407
+ if (currentState.internal) {
2408
+ currentState.internal.priority--;
2409
+ let parentRoot = currentState.previousRoot;
2410
+ while (parentRoot) {
2411
+ const parentState = parentRoot.getState();
2412
+ if (parentState?.internal) parentState.internal.priority--;
2413
+ parentRoot = parentState?.previousRoot;
2414
+ }
2079
2415
  }
2080
2416
  }
2417
+ };
2418
+ } else {
2419
+ const registerOutside = () => {
2420
+ return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2421
+ };
2422
+ if (scheduler.independent || scheduler.isReady) {
2423
+ return registerOutside();
2081
2424
  }
2082
- };
2083
- }, [store, id, optionsKey, isLegacyPriority]);
2425
+ let unregisterJob = null;
2426
+ const unsubReady = scheduler.onRootReady(() => {
2427
+ unregisterJob = registerOutside();
2428
+ });
2429
+ return () => {
2430
+ unsubReady();
2431
+ unregisterJob?.();
2432
+ };
2433
+ }
2434
+ }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2084
2435
  const isPaused = React__namespace.useSyncExternalStore(
2085
2436
  // Subscribe function
2086
2437
  React__namespace.useCallback(
@@ -2095,7 +2446,7 @@ function useFrame(callback, priorityOrOptions) {
2095
2446
  React__namespace.useCallback(() => false, [])
2096
2447
  );
2097
2448
  const controls = React__namespace.useMemo(() => {
2098
- const scheduler = getScheduler();
2449
+ const scheduler2 = getScheduler();
2099
2450
  return {
2100
2451
  /** The job's unique ID */
2101
2452
  id,
@@ -2103,7 +2454,7 @@ function useFrame(callback, priorityOrOptions) {
2103
2454
  * Access to the global scheduler for frame loop control.
2104
2455
  * Use for controlling the entire frame loop, adding phases, etc.
2105
2456
  */
2106
- scheduler,
2457
+ scheduler: scheduler2,
2107
2458
  /**
2108
2459
  * Manually step this job only.
2109
2460
  * Bypasses FPS limiting - always runs.
@@ -2351,6 +2702,18 @@ function useTextures() {
2351
2702
  }, [store]);
2352
2703
  }
2353
2704
 
2705
+ function useRenderTarget(width, height, options) {
2706
+ const isLegacy = useThree((s) => s.isLegacy);
2707
+ const size = useThree((s) => s.size);
2708
+ return React.useMemo(() => {
2709
+ const w = width ?? size.width;
2710
+ const h = height ?? size.height;
2711
+ {
2712
+ return isLegacy ? new three.WebGLRenderTarget(w, h, options) : new webgpu.RenderTarget(w, h, options);
2713
+ }
2714
+ }, [width, height, size.width, size.height, options, isLegacy]);
2715
+ }
2716
+
2354
2717
  function useStore() {
2355
2718
  const store = React.useContext(context);
2356
2719
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
@@ -2402,7 +2765,7 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
2402
2765
  getScheduler().step(timestamp);
2403
2766
  }
2404
2767
 
2405
- const version = "10.0.0-alpha.0";
2768
+ const version = "10.0.0-alpha.1";
2406
2769
  const packageData = {
2407
2770
  version: version};
2408
2771
 
@@ -13703,7 +14066,8 @@ function createReconciler(config) {
13703
14066
  return reconciler2;
13704
14067
  }
13705
14068
  const NoEventPriority = 0;
13706
- const catalogue = {};
14069
+ const R3F_CATALOGUE = Symbol.for("@react-three/fiber.catalogue");
14070
+ const catalogue = globalThis[R3F_CATALOGUE] ?? (globalThis[R3F_CATALOGUE] = {});
13707
14071
  const PREFIX_REGEX = /^three(?=[A-Z])/;
13708
14072
  const toPascalCase = (type) => `${type[0].toUpperCase()}${type.slice(1)}`;
13709
14073
  let i = 0;
@@ -14201,7 +14565,9 @@ function createRoot(canvas) {
14201
14565
  camera: cameraOptions,
14202
14566
  onPointerMissed,
14203
14567
  onDragOverMissed,
14204
- onDropMissed
14568
+ onDropMissed,
14569
+ autoUpdateFrustum = true,
14570
+ occlusion = false
14205
14571
  } = props;
14206
14572
  let state = store.getState();
14207
14573
  const defaultGLProps = {
@@ -14231,7 +14597,9 @@ function createRoot(canvas) {
14231
14597
  state.set({ isLegacy: true, gl: renderer, renderer });
14232
14598
  } else if (!wantsGL && !state.internal.actualRenderer) {
14233
14599
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14234
- await renderer.init();
14600
+ if (!renderer.hasInitialized?.()) {
14601
+ await renderer.init();
14602
+ }
14235
14603
  const backend = renderer.backend;
14236
14604
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14237
14605
  state.internal.actualRenderer = renderer;
@@ -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) });
@@ -14306,6 +14676,10 @@ function createRoot(canvas) {
14306
14676
  if (!state.onPointerMissed) state.set({ onPointerMissed });
14307
14677
  if (!state.onDragOverMissed) state.set({ onDragOverMissed });
14308
14678
  if (!state.onDropMissed) state.set({ onDropMissed });
14679
+ if (state.autoUpdateFrustum !== autoUpdateFrustum) state.set({ autoUpdateFrustum });
14680
+ if (occlusion && !state.internal.occlusionEnabled) {
14681
+ enableOcclusion(store);
14682
+ }
14309
14683
  if (performance && !is.equ(performance, lastConfiguredProps.performance, shallowLoose)) {
14310
14684
  state.set((state2) => ({ performance: { ...state2.performance, ...performance } }));
14311
14685
  lastConfiguredProps.performance = performance;
@@ -14406,7 +14780,37 @@ function createRoot(canvas) {
14406
14780
  const rootId = state.internal.rootId;
14407
14781
  if (!rootId) {
14408
14782
  const newRootId = scheduler.generateRootId();
14409
- const unregisterRoot = scheduler.registerRoot(newRootId, () => store.getState());
14783
+ const unregisterRoot = scheduler.registerRoot(newRootId, {
14784
+ getState: () => store.getState(),
14785
+ onError: (err) => store.getState().setError(err)
14786
+ });
14787
+ const unregisterFrustum = scheduler.register(
14788
+ () => {
14789
+ const state2 = store.getState();
14790
+ if (state2.autoUpdateFrustum && state2.camera) {
14791
+ updateFrustum(state2.camera, state2.frustum);
14792
+ }
14793
+ },
14794
+ {
14795
+ id: `${newRootId}_frustum`,
14796
+ rootId: newRootId,
14797
+ phase: "preRender",
14798
+ system: true
14799
+ }
14800
+ );
14801
+ const unregisterVisibility = scheduler.register(
14802
+ () => {
14803
+ const state2 = store.getState();
14804
+ checkVisibility(state2);
14805
+ },
14806
+ {
14807
+ id: `${newRootId}_visibility`,
14808
+ rootId: newRootId,
14809
+ phase: "preRender",
14810
+ system: true,
14811
+ after: `${newRootId}_frustum`
14812
+ }
14813
+ );
14410
14814
  const unregisterRender = scheduler.register(
14411
14815
  () => {
14412
14816
  const state2 = store.getState();
@@ -14434,6 +14838,8 @@ function createRoot(canvas) {
14434
14838
  rootId: newRootId,
14435
14839
  unregisterRoot: () => {
14436
14840
  unregisterRoot();
14841
+ unregisterFrustum();
14842
+ unregisterVisibility();
14437
14843
  unregisterRender();
14438
14844
  },
14439
14845
  scheduler
@@ -14491,6 +14897,7 @@ function unmountComponentAtNode(canvas, callback) {
14491
14897
  const unregisterRoot = state.internal.unregisterRoot;
14492
14898
  if (unregisterRoot) unregisterRoot();
14493
14899
  state.events.disconnect?.();
14900
+ cleanupHelperGroup(root.store);
14494
14901
  renderer?.renderLists?.dispose?.();
14495
14902
  renderer?.forceContextLoss?.();
14496
14903
  if (renderer?.xr) state.xr.disconnect();
@@ -14670,7 +15077,22 @@ function CanvasImpl({
14670
15077
  effectActiveRef.current = true;
14671
15078
  const canvas = canvasRef.current;
14672
15079
  if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
14673
- if (!root.current) root.current = createRoot(canvas);
15080
+ if (!root.current) {
15081
+ root.current = createRoot(canvas);
15082
+ notifyAlpha({
15083
+ message: "React Three Fiber v10 is in ALPHA - expect breaking changes",
15084
+ link: "https://github.com/pmndrs/react-three-fiber/discussions"
15085
+ });
15086
+ const rootEntry = _roots.get(canvas);
15087
+ if (rootEntry?.store) {
15088
+ if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
15089
+ unsubscribeErrorRef.current = rootEntry.store.subscribe((state) => {
15090
+ if (state.error && effectActiveRef.current) {
15091
+ setError(state.error);
15092
+ }
15093
+ });
15094
+ }
15095
+ }
14674
15096
  async function run() {
14675
15097
  if (!effectActiveRef.current || !root.current) return;
14676
15098
  await root.current.configure({
@@ -14711,15 +15133,9 @@ function CanvasImpl({
14711
15133
  }
14712
15134
  });
14713
15135
  if (!effectActiveRef.current || !root.current) return;
14714
- const store = root.current.render(
15136
+ root.current.render(
14715
15137
  /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: children ?? null }) }) })
14716
15138
  );
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
15139
  }
14724
15140
  run();
14725
15141
  }
@@ -14819,6 +15235,7 @@ exports.removeInteractivity = removeInteractivity;
14819
15235
  exports.resolve = resolve;
14820
15236
  exports.unmountComponentAtNode = unmountComponentAtNode;
14821
15237
  exports.updateCamera = updateCamera;
15238
+ exports.updateFrustum = updateFrustum;
14822
15239
  exports.useBridge = useBridge;
14823
15240
  exports.useFrame = useFrame;
14824
15241
  exports.useGraph = useGraph;
@@ -14826,6 +15243,7 @@ exports.useInstanceHandle = useInstanceHandle;
14826
15243
  exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
14827
15244
  exports.useLoader = useLoader;
14828
15245
  exports.useMutableCallback = useMutableCallback;
15246
+ exports.useRenderTarget = useRenderTarget;
14829
15247
  exports.useStore = useStore;
14830
15248
  exports.useTexture = useTexture;
14831
15249
  exports.useTextures = useTextures;