@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.
@@ -1,5 +1,5 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
2
+ import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
3
3
  import { Inspector } from 'three/addons/inspector/Inspector.js';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
  import * as React from 'react';
@@ -34,12 +34,15 @@ const WebGLRenderer = class WebGLRenderer2 {
34
34
  );
35
35
  }
36
36
  };
37
+ const WebGLRenderTarget = null;
37
38
 
38
39
  const THREE = /*#__PURE__*/_mergeNamespaces({
39
40
  __proto__: null,
40
41
  Inspector: Inspector,
41
42
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
42
43
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
44
+ RenderTargetCompat: RenderTarget,
45
+ WebGLRenderTarget: WebGLRenderTarget,
43
46
  WebGLRenderer: WebGLRenderer
44
47
  }, [webgpu]);
45
48
 
@@ -148,6 +151,13 @@ function updateCamera(camera, size) {
148
151
  }
149
152
  camera.updateProjectionMatrix();
150
153
  }
154
+ const frustumMatrix = new Matrix4();
155
+ function updateFrustum(camera, frustum) {
156
+ const target = frustum ?? new Frustum();
157
+ frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
158
+ target.setFromProjectionMatrix(frustumMatrix);
159
+ return target;
160
+ }
151
161
 
152
162
  const REACT_INTERNAL_PROPS = ["children", "key", "ref"];
153
163
  function findInitialRoot(instance) {
@@ -228,6 +238,205 @@ function invalidateInstance(instance) {
228
238
  if (state && state.internal.frames === 0) state.invalidate();
229
239
  }
230
240
 
241
+ const tempFrustum = new Frustum();
242
+ let hasWarnedWebGL = false;
243
+ let tslModule = null;
244
+ async function loadTSL() {
245
+ if (tslModule) return tslModule;
246
+ try {
247
+ const tsl = await import('three/tsl');
248
+ tslModule = { uniform: tsl.uniform, nodeObject: tsl.nodeObject };
249
+ return tslModule;
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+ function createOcclusionObserverNode(store, uniform) {
255
+ const node = new Node("float");
256
+ node.updateType = NodeUpdateType.OBJECT;
257
+ node.update = function(frame) {
258
+ const { internal } = store.getState();
259
+ const registry = internal.visibilityRegistry;
260
+ const cache = internal.occlusionCache;
261
+ for (const entry of registry.values()) {
262
+ const { object, handlers } = entry;
263
+ if (handlers.onOccluded || handlers.onVisible) {
264
+ const isOccluded = frame.renderer.isOccluded(object);
265
+ cache.set(object, isOccluded);
266
+ }
267
+ }
268
+ };
269
+ node.setup = function() {
270
+ return uniform(0);
271
+ };
272
+ return node;
273
+ }
274
+ let occlusionSetupPromise = null;
275
+ function enableOcclusion(store) {
276
+ const state = store.getState();
277
+ const { internal, renderer, rootScene } = state;
278
+ if (internal.occlusionEnabled || occlusionSetupPromise) return;
279
+ const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
280
+ if (!hasOcclusionSupport) {
281
+ if (!hasWarnedWebGL) {
282
+ console.warn(
283
+ "[R3F] Warning: onOccluded/onVisible occlusion queries require WebGPU renderer. Occlusion events will not fire on WebGL."
284
+ );
285
+ hasWarnedWebGL = true;
286
+ }
287
+ return;
288
+ }
289
+ occlusionSetupPromise = setupOcclusion(store);
290
+ }
291
+ async function setupOcclusion(store) {
292
+ const state = store.getState();
293
+ const { internal, rootScene, set } = state;
294
+ const tsl = await loadTSL();
295
+ if (!tsl) {
296
+ console.warn("[R3F] Warning: TSL module not available. Occlusion queries disabled.");
297
+ occlusionSetupPromise = null;
298
+ return;
299
+ }
300
+ const { uniform, nodeObject } = tsl;
301
+ let helperGroup = internal.helperGroup;
302
+ if (!helperGroup) {
303
+ helperGroup = new Group();
304
+ helperGroup.name = "__r3fInternal";
305
+ helperGroup.__r3fInternal = true;
306
+ rootScene.add(helperGroup);
307
+ }
308
+ const geometry = new BoxGeometry(1, 1, 1);
309
+ const material = new MeshBasicNodeMaterial({
310
+ transparent: true,
311
+ opacity: 0
312
+ });
313
+ const observerNode = nodeObject(createOcclusionObserverNode(store, uniform));
314
+ material.colorNode = observerNode;
315
+ material.needsUpdate = true;
316
+ const mesh = new Mesh(geometry, material);
317
+ mesh.name = "__r3fOcclusionObserver";
318
+ mesh.scale.setScalar(1e-4);
319
+ mesh.frustumCulled = false;
320
+ mesh.__r3fInternal = true;
321
+ helperGroup.add(mesh);
322
+ set((state2) => ({
323
+ internal: {
324
+ ...state2.internal,
325
+ helperGroup,
326
+ occlusionObserver: mesh,
327
+ occlusionEnabled: true
328
+ }
329
+ }));
330
+ occlusionSetupPromise = null;
331
+ }
332
+ function disableOcclusion(store) {
333
+ const { internal, set } = store.getState();
334
+ if (!internal.occlusionEnabled) return;
335
+ if (internal.occlusionObserver) {
336
+ internal.occlusionObserver.removeFromParent();
337
+ internal.occlusionObserver.geometry.dispose();
338
+ internal.occlusionObserver.material.dispose();
339
+ }
340
+ internal.occlusionCache.clear();
341
+ set((state) => ({
342
+ internal: {
343
+ ...state.internal,
344
+ occlusionObserver: null,
345
+ occlusionEnabled: false
346
+ }
347
+ }));
348
+ }
349
+ function cleanupHelperGroup(store) {
350
+ const { internal, set } = store.getState();
351
+ disableOcclusion(store);
352
+ if (internal.helperGroup) {
353
+ internal.helperGroup.removeFromParent();
354
+ set((state) => ({
355
+ internal: {
356
+ ...state.internal,
357
+ helperGroup: null
358
+ }
359
+ }));
360
+ }
361
+ }
362
+ function registerVisibility(store, object, handlers) {
363
+ const { internal } = store.getState();
364
+ const registry = internal.visibilityRegistry;
365
+ const entry = {
366
+ object,
367
+ handlers,
368
+ lastFramedState: null,
369
+ lastOccludedState: null,
370
+ lastVisibleState: null
371
+ };
372
+ registry.set(object.uuid, entry);
373
+ if (handlers.onOccluded || handlers.onVisible) {
374
+ object.occlusionTest = true;
375
+ if (!internal.occlusionEnabled) {
376
+ enableOcclusion(store);
377
+ }
378
+ }
379
+ }
380
+ function unregisterVisibility(store, object) {
381
+ const { internal } = store.getState();
382
+ internal.visibilityRegistry.delete(object.uuid);
383
+ internal.occlusionCache.delete(object);
384
+ }
385
+ function checkVisibility(state) {
386
+ const { internal, camera } = state;
387
+ const registry = internal.visibilityRegistry;
388
+ if (registry.size === 0) return;
389
+ updateFrustum(camera, tempFrustum);
390
+ for (const entry of registry.values()) {
391
+ const { object, handlers, lastFramedState, lastOccludedState, lastVisibleState } = entry;
392
+ let inFrustum = null;
393
+ const computeFrustum = () => {
394
+ if (inFrustum === null) {
395
+ if (object.geometry?.boundingSphere === null) {
396
+ object.geometry?.computeBoundingSphere();
397
+ }
398
+ inFrustum = tempFrustum.intersectsObject(object);
399
+ }
400
+ return inFrustum;
401
+ };
402
+ if (handlers.onFramed) {
403
+ const currentInFrustum = computeFrustum();
404
+ if (currentInFrustum !== lastFramedState) {
405
+ entry.lastFramedState = currentInFrustum;
406
+ handlers.onFramed(currentInFrustum);
407
+ }
408
+ }
409
+ let currentOcclusion = null;
410
+ if (handlers.onOccluded && internal.occlusionEnabled) {
411
+ currentOcclusion = internal.occlusionCache.get(object) ?? null;
412
+ if (currentOcclusion !== null && currentOcclusion !== lastOccludedState) {
413
+ entry.lastOccludedState = currentOcclusion;
414
+ handlers.onOccluded(currentOcclusion);
415
+ }
416
+ }
417
+ if (handlers.onVisible) {
418
+ const currentInFrustum = computeFrustum();
419
+ if (!handlers.onFramed && currentInFrustum !== lastFramedState) {
420
+ entry.lastFramedState = currentInFrustum;
421
+ }
422
+ let isOccluded = currentOcclusion;
423
+ if (isOccluded === null && internal.occlusionEnabled) {
424
+ isOccluded = internal.occlusionCache.get(object) ?? null;
425
+ }
426
+ if (isOccluded === null) isOccluded = false;
427
+ const isVisible = currentInFrustum && !isOccluded && object.visible;
428
+ if (isVisible !== lastVisibleState) {
429
+ entry.lastVisibleState = isVisible;
430
+ handlers.onVisible(isVisible);
431
+ }
432
+ }
433
+ }
434
+ }
435
+ function hasVisibilityHandlers(handlers) {
436
+ if (!handlers) return false;
437
+ return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
438
+ }
439
+
231
440
  const RESERVED_PROPS = [
232
441
  "children",
233
442
  "key",
@@ -242,6 +451,7 @@ const RESERVED_PROPS = [
242
451
  "dispose"
243
452
  ];
244
453
  const EVENT_REGEX = /^on(Pointer|Drag|Drop|Click|DoubleClick|ContextMenu|Wheel)/;
454
+ const VISIBILITY_EVENT_REGEX = /^on(Framed|Occluded|Visible)$/;
245
455
  const INDEX_REGEX = /-\d+$/;
246
456
  const MEMOIZED_PROTOTYPES = /* @__PURE__ */ new Map();
247
457
  const colorMaps = ["map", "emissiveMap", "sheenColorMap", "specularColorMap", "envMap"];
@@ -336,6 +546,12 @@ function applyProps(object, props) {
336
546
  instance.eventCount = Object.keys(instance.handlers).length;
337
547
  continue;
338
548
  }
549
+ if (instance && VISIBILITY_EVENT_REGEX.test(prop)) {
550
+ if (typeof value === "function") instance.handlers[prop] = value;
551
+ else delete instance.handlers[prop];
552
+ instance.eventCount = Object.keys(instance.handlers).length;
553
+ continue;
554
+ }
339
555
  if (value === void 0) continue;
340
556
  let { root, key, target } = resolve(object, prop);
341
557
  if (target === void 0 && (typeof root !== "object" || root === null)) {
@@ -372,6 +588,17 @@ function applyProps(object, props) {
372
588
  if (instance.eventCount && object2.raycast !== null) {
373
589
  rootState.internal.interaction.push(object2);
374
590
  }
591
+ const root = findInitialRoot(instance);
592
+ const visibilityHandlers = {
593
+ onFramed: instance.handlers.onFramed,
594
+ onOccluded: instance.handlers.onOccluded,
595
+ onVisible: instance.handlers.onVisible
596
+ };
597
+ if (hasVisibilityHandlers(visibilityHandlers)) {
598
+ registerVisibility(root, object2, visibilityHandlers);
599
+ } else {
600
+ unregisterVisibility(root, object2);
601
+ }
375
602
  }
376
603
  if (instance && instance.props.attach === void 0) {
377
604
  if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
@@ -406,6 +633,7 @@ function removeInteractivity(store, object) {
406
633
  internal.capturedMap.forEach((captures, pointerId) => {
407
634
  releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
408
635
  });
636
+ unregisterVisibility(store, object);
409
637
  }
410
638
  function createEvents(store) {
411
639
  function calculateDistance(event) {
@@ -788,8 +1016,21 @@ function formatLocation(url, line) {
788
1016
  const file = clean.split("/").pop() ?? clean;
789
1017
  return `${file}:${line}`;
790
1018
  }
1019
+ function notifyAlpha({ message, link }) {
1020
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
1021
+ return;
1022
+ }
1023
+ if (shownNotices.has(message)) return;
1024
+ shownNotices.add(message);
1025
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
1026
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
1027
+ {
1028
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
1029
+ }
1030
+ }
791
1031
 
792
- const context = /* @__PURE__ */ React.createContext(null);
1032
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1033
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
793
1034
  const createStore = (invalidate, advance) => {
794
1035
  const rootStore = createWithEqualityFn((set, get) => {
795
1036
  const position = new Vector3();
@@ -820,6 +1061,8 @@ const createStore = (invalidate, advance) => {
820
1061
  gl: null,
821
1062
  renderer: null,
822
1063
  camera: null,
1064
+ frustum: new Frustum(),
1065
+ autoUpdateFrustum: true,
823
1066
  raycaster: null,
824
1067
  events: { priority: 1, enabled: true, connected: false },
825
1068
  scene: null,
@@ -901,6 +1144,13 @@ const createStore = (invalidate, advance) => {
901
1144
  initialHits: [],
902
1145
  capturedMap: /* @__PURE__ */ new Map(),
903
1146
  lastEvent: React.createRef(),
1147
+ // Visibility tracking (onFramed, onOccluded, onVisible)
1148
+ visibilityRegistry: /* @__PURE__ */ new Map(),
1149
+ // Occlusion system (WebGPU only)
1150
+ occlusionEnabled: false,
1151
+ occlusionObserver: null,
1152
+ occlusionCache: /* @__PURE__ */ new Map(),
1153
+ helperGroup: null,
904
1154
  // Updates
905
1155
  active: false,
906
1156
  frames: 0,
@@ -986,7 +1236,15 @@ const createStore = (invalidate, advance) => {
986
1236
  }
987
1237
  if (camera !== oldCamera) {
988
1238
  oldCamera = camera;
1239
+ const { rootScene } = rootStore.getState();
1240
+ if (camera && rootScene && !camera.parent) {
1241
+ rootScene.add(camera);
1242
+ }
989
1243
  set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1244
+ const currentState = rootStore.getState();
1245
+ if (currentState.autoUpdateFrustum && camera) {
1246
+ updateFrustum(camera, currentState.frustum);
1247
+ }
990
1248
  }
991
1249
  });
992
1250
  rootStore.subscribe((state2) => invalidate(state2));
@@ -1350,6 +1608,10 @@ const _Scheduler = class _Scheduler {
1350
1608
  __publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
1351
1609
  __publicField(this, "pendingFrames", 0);
1352
1610
  __publicField(this, "_frameloop", "always");
1611
+ //* Independent Mode & Error Handling State ================================
1612
+ __publicField(this, "_independent", false);
1613
+ __publicField(this, "errorHandler", null);
1614
+ __publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
1353
1615
  //* Core Loop Execution Methods ================================
1354
1616
  /**
1355
1617
  * Main RAF loop callback.
@@ -1372,6 +1634,12 @@ const _Scheduler = class _Scheduler {
1372
1634
  });
1373
1635
  this.phaseGraph = new PhaseGraph();
1374
1636
  }
1637
+ static get instance() {
1638
+ return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
1639
+ }
1640
+ static set instance(value) {
1641
+ globalThis[_Scheduler.INSTANCE_KEY] = value;
1642
+ }
1375
1643
  /**
1376
1644
  * Get the global scheduler instance (creates if doesn't exist).
1377
1645
  * Uses HMR data to preserve instance across hot reloads.
@@ -1420,29 +1688,43 @@ const _Scheduler = class _Scheduler {
1420
1688
  get isRunning() {
1421
1689
  return this.loopState.running;
1422
1690
  }
1691
+ get isReady() {
1692
+ return this.roots.size > 0;
1693
+ }
1694
+ get independent() {
1695
+ return this._independent;
1696
+ }
1697
+ set independent(value) {
1698
+ this._independent = value;
1699
+ if (value) this.ensureDefaultRoot();
1700
+ }
1423
1701
  //* Root Management Methods ================================
1424
1702
  /**
1425
1703
  * Register a root (Canvas) with the scheduler.
1426
1704
  * The first root to register starts the RAF loop (if frameloop='always').
1427
1705
  * @param {string} id - Unique identifier for this root
1428
- * @param {() => RootState} getState - Function to get the root's current state
1706
+ * @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
1429
1707
  * @returns {() => void} Unsubscribe function to remove this root
1430
1708
  */
1431
- registerRoot(id, getState) {
1709
+ registerRoot(id, options = {}) {
1432
1710
  if (this.roots.has(id)) {
1433
1711
  console.warn(`[Scheduler] Root "${id}" already registered`);
1434
1712
  return () => this.unregisterRoot(id);
1435
1713
  }
1436
1714
  const entry = {
1437
1715
  id,
1438
- getState,
1716
+ getState: options.getState ?? (() => ({})),
1439
1717
  jobs: /* @__PURE__ */ new Map(),
1440
1718
  sortedJobs: [],
1441
1719
  needsRebuild: false
1442
1720
  };
1721
+ if (options.onError) {
1722
+ this.errorHandler = options.onError;
1723
+ }
1443
1724
  this.roots.set(id, entry);
1444
- if (this.roots.size === 1 && this._frameloop === "always") {
1445
- this.start();
1725
+ if (this.roots.size === 1) {
1726
+ this.notifyRootReady();
1727
+ if (this._frameloop === "always") this.start();
1446
1728
  }
1447
1729
  return () => this.unregisterRoot(id);
1448
1730
  }
@@ -1462,7 +1744,60 @@ const _Scheduler = class _Scheduler {
1462
1744
  this.roots.delete(id);
1463
1745
  if (this.roots.size === 0) {
1464
1746
  this.stop();
1747
+ this.errorHandler = null;
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Subscribe to be notified when a root becomes available.
1752
+ * Fires immediately if a root already exists.
1753
+ * @param {() => void} callback - Function called when first root registers
1754
+ * @returns {() => void} Unsubscribe function
1755
+ */
1756
+ onRootReady(callback) {
1757
+ if (this.roots.size > 0) {
1758
+ callback();
1759
+ return () => {
1760
+ };
1465
1761
  }
1762
+ this.rootReadyCallbacks.add(callback);
1763
+ return () => this.rootReadyCallbacks.delete(callback);
1764
+ }
1765
+ /**
1766
+ * Notify all registered root-ready callbacks.
1767
+ * Called when the first root registers.
1768
+ * @returns {void}
1769
+ * @private
1770
+ */
1771
+ notifyRootReady() {
1772
+ for (const cb of this.rootReadyCallbacks) {
1773
+ try {
1774
+ cb();
1775
+ } catch (error) {
1776
+ console.error("[Scheduler] Error in root-ready callback:", error);
1777
+ }
1778
+ }
1779
+ this.rootReadyCallbacks.clear();
1780
+ }
1781
+ /**
1782
+ * Ensure a default root exists for independent mode.
1783
+ * Creates a minimal root with no state provider.
1784
+ * @returns {void}
1785
+ * @private
1786
+ */
1787
+ ensureDefaultRoot() {
1788
+ if (!this.roots.has("__default__")) {
1789
+ this.registerRoot("__default__");
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Trigger error handling for job errors.
1794
+ * Uses the bound error handler if available, otherwise logs to console.
1795
+ * @param {Error} error - The error to handle
1796
+ * @returns {void}
1797
+ */
1798
+ triggerError(error) {
1799
+ if (this.errorHandler) this.errorHandler(error);
1800
+ else console.error("[Scheduler]", error);
1466
1801
  }
1467
1802
  //* Phase Management Methods ================================
1468
1803
  /**
@@ -1822,9 +2157,9 @@ const _Scheduler = class _Scheduler {
1822
2157
  const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
1823
2158
  const delta = deltaMs / 1e3;
1824
2159
  const elapsed = now - this.loopState.createdAt;
1825
- const rootState = root.getState();
2160
+ const providedState = root.getState?.() ?? {};
1826
2161
  const frameState = {
1827
- ...rootState,
2162
+ ...providedState,
1828
2163
  time: now,
1829
2164
  delta,
1830
2165
  elapsed,
@@ -1834,6 +2169,7 @@ const _Scheduler = class _Scheduler {
1834
2169
  job.callback(frameState, delta);
1835
2170
  } catch (error) {
1836
2171
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
2172
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1837
2173
  }
1838
2174
  }
1839
2175
  /**
@@ -1875,7 +2211,7 @@ const _Scheduler = class _Scheduler {
1875
2211
  /**
1876
2212
  * Execute all jobs for a single root in sorted order.
1877
2213
  * Rebuilds sorted job list if needed, then dispatches each job.
1878
- * Errors are caught and propagated to the root's error boundary.
2214
+ * Errors are caught and propagated via triggerError.
1879
2215
  * @param {RootEntry} root - The root entry to tick
1880
2216
  * @param {number} timestamp - RAF timestamp in milliseconds
1881
2217
  * @param {number} delta - Time since last frame in seconds
@@ -1887,10 +2223,9 @@ const _Scheduler = class _Scheduler {
1887
2223
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
1888
2224
  root.needsRebuild = false;
1889
2225
  }
1890
- const rootState = root.getState();
1891
- if (!rootState) return;
2226
+ const providedState = root.getState?.() ?? {};
1892
2227
  const frameState = {
1893
- ...rootState,
2228
+ ...providedState,
1894
2229
  time: timestamp,
1895
2230
  delta,
1896
2231
  elapsed: this.loopState.elapsedTime / 1e3,
@@ -1903,7 +2238,7 @@ const _Scheduler = class _Scheduler {
1903
2238
  job.callback(frameState, delta);
1904
2239
  } catch (error) {
1905
2240
  console.error(`[Scheduler] Error in job "${job.id}":`, error);
1906
- rootState.setError(error instanceof Error ? error : new Error(String(error)));
2241
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
1907
2242
  }
1908
2243
  }
1909
2244
  }
@@ -1988,8 +2323,11 @@ const _Scheduler = class _Scheduler {
1988
2323
  return /* @__PURE__ */ new Set([value]);
1989
2324
  }
1990
2325
  };
1991
- //* Static State & Methods (Singlton Usage) ================================
1992
- __publicField(_Scheduler, "instance", null);
2326
+ //* Static State & Methods (Singleton Usage) ================================
2327
+ //* Cross-Bundle Singleton Key ==============================
2328
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2329
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2330
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
1993
2331
  let Scheduler = _Scheduler;
1994
2332
  const getScheduler = () => Scheduler.get();
1995
2333
  if (hmrData) {
@@ -1997,11 +2335,9 @@ if (hmrData) {
1997
2335
  }
1998
2336
 
1999
2337
  function useFrame(callback, priorityOrOptions) {
2000
- const store = useStore();
2001
- const getRootId = React.useCallback(() => {
2002
- const state = store.getState();
2003
- return state.internal.rootId;
2004
- }, [store]);
2338
+ const store = React.useContext(context);
2339
+ const isInsideCanvas = store !== null;
2340
+ const scheduler = getScheduler();
2005
2341
  const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
2006
2342
  id: priorityOrOptions.id,
2007
2343
  phase: priorityOrOptions.phase,
@@ -2021,55 +2357,71 @@ function useFrame(callback, priorityOrOptions) {
2021
2357
  const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
2022
2358
  useIsomorphicLayoutEffect(() => {
2023
2359
  if (!callback) return;
2024
- const scheduler = getScheduler();
2025
- const rootId = getRootId();
2026
- const state = store.getState();
2027
- if (isLegacyPriority) {
2028
- state.internal.priority++;
2029
- let parentRoot = state.previousRoot;
2030
- while (parentRoot) {
2031
- const parentState = parentRoot.getState();
2032
- if (parentState?.internal) parentState.internal.priority++;
2033
- parentRoot = parentState?.previousRoot;
2034
- }
2035
- notifyDepreciated({
2036
- heading: "useFrame with numeric priority is deprecated",
2037
- 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 })',
2038
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2039
- });
2040
- }
2041
- const wrappedCallback = (frameState, delta) => {
2042
- const localState = store.getState();
2043
- const mergedState = {
2044
- ...localState,
2045
- time: frameState.time,
2046
- delta: frameState.delta,
2047
- elapsed: frameState.elapsed,
2048
- frame: frameState.frame
2049
- };
2050
- callbackRef.current?.(mergedState, delta);
2051
- };
2052
- const unregister = scheduler.register(wrappedCallback, {
2053
- id,
2054
- rootId,
2055
- ...options
2056
- });
2057
- return () => {
2058
- unregister();
2360
+ if (isInsideCanvas) {
2361
+ const state = store.getState();
2362
+ const rootId = state.internal.rootId;
2059
2363
  if (isLegacyPriority) {
2060
- const currentState = store.getState();
2061
- if (currentState.internal) {
2062
- currentState.internal.priority--;
2063
- let parentRoot = currentState.previousRoot;
2064
- while (parentRoot) {
2065
- const parentState = parentRoot.getState();
2066
- if (parentState?.internal) parentState.internal.priority--;
2067
- parentRoot = parentState?.previousRoot;
2364
+ state.internal.priority++;
2365
+ let parentRoot = state.previousRoot;
2366
+ while (parentRoot) {
2367
+ const parentState = parentRoot.getState();
2368
+ if (parentState?.internal) parentState.internal.priority++;
2369
+ parentRoot = parentState?.previousRoot;
2370
+ }
2371
+ notifyDepreciated({
2372
+ heading: "useFrame with numeric priority is deprecated",
2373
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
2374
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
2375
+ });
2376
+ }
2377
+ const wrappedCallback = (frameState, delta) => {
2378
+ const localState = store.getState();
2379
+ const mergedState = {
2380
+ ...localState,
2381
+ time: frameState.time,
2382
+ delta: frameState.delta,
2383
+ elapsed: frameState.elapsed,
2384
+ frame: frameState.frame
2385
+ };
2386
+ callbackRef.current?.(mergedState, delta);
2387
+ };
2388
+ const unregister = scheduler.register(wrappedCallback, {
2389
+ id,
2390
+ rootId,
2391
+ ...options
2392
+ });
2393
+ return () => {
2394
+ unregister();
2395
+ if (isLegacyPriority) {
2396
+ const currentState = store.getState();
2397
+ if (currentState.internal) {
2398
+ currentState.internal.priority--;
2399
+ let parentRoot = currentState.previousRoot;
2400
+ while (parentRoot) {
2401
+ const parentState = parentRoot.getState();
2402
+ if (parentState?.internal) parentState.internal.priority--;
2403
+ parentRoot = parentState?.previousRoot;
2404
+ }
2068
2405
  }
2069
2406
  }
2407
+ };
2408
+ } else {
2409
+ const registerOutside = () => {
2410
+ return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
2411
+ };
2412
+ if (scheduler.independent || scheduler.isReady) {
2413
+ return registerOutside();
2070
2414
  }
2071
- };
2072
- }, [store, id, optionsKey, isLegacyPriority]);
2415
+ let unregisterJob = null;
2416
+ const unsubReady = scheduler.onRootReady(() => {
2417
+ unregisterJob = registerOutside();
2418
+ });
2419
+ return () => {
2420
+ unsubReady();
2421
+ unregisterJob?.();
2422
+ };
2423
+ }
2424
+ }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
2073
2425
  const isPaused = React.useSyncExternalStore(
2074
2426
  // Subscribe function
2075
2427
  React.useCallback(
@@ -2084,7 +2436,7 @@ function useFrame(callback, priorityOrOptions) {
2084
2436
  React.useCallback(() => false, [])
2085
2437
  );
2086
2438
  const controls = React.useMemo(() => {
2087
- const scheduler = getScheduler();
2439
+ const scheduler2 = getScheduler();
2088
2440
  return {
2089
2441
  /** The job's unique ID */
2090
2442
  id,
@@ -2092,7 +2444,7 @@ function useFrame(callback, priorityOrOptions) {
2092
2444
  * Access to the global scheduler for frame loop control.
2093
2445
  * Use for controlling the entire frame loop, adding phases, etc.
2094
2446
  */
2095
- scheduler,
2447
+ scheduler: scheduler2,
2096
2448
  /**
2097
2449
  * Manually step this job only.
2098
2450
  * Bypasses FPS limiting - always runs.
@@ -2340,6 +2692,16 @@ function useTextures() {
2340
2692
  }, [store]);
2341
2693
  }
2342
2694
 
2695
+ function useRenderTarget(width, height, options) {
2696
+ const isLegacy = useThree((s) => s.isLegacy);
2697
+ const size = useThree((s) => s.size);
2698
+ return useMemo(() => {
2699
+ const w = width ?? size.width;
2700
+ const h = height ?? size.height;
2701
+ return new RenderTarget(w, h, options);
2702
+ }, [width, height, size.width, size.height, options, isLegacy]);
2703
+ }
2704
+
2343
2705
  function useStore() {
2344
2706
  const store = useContext(context);
2345
2707
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
@@ -2391,7 +2753,7 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
2391
2753
  getScheduler().step(timestamp);
2392
2754
  }
2393
2755
 
2394
- const version = "10.0.0-alpha.0";
2756
+ const version = "10.0.0-alpha.1";
2395
2757
  const packageData = {
2396
2758
  version: version};
2397
2759
 
@@ -13692,7 +14054,8 @@ function createReconciler(config) {
13692
14054
  return reconciler2;
13693
14055
  }
13694
14056
  const NoEventPriority = 0;
13695
- const catalogue = {};
14057
+ const R3F_CATALOGUE = Symbol.for("@react-three/fiber.catalogue");
14058
+ const catalogue = globalThis[R3F_CATALOGUE] ?? (globalThis[R3F_CATALOGUE] = {});
13696
14059
  const PREFIX_REGEX = /^three(?=[A-Z])/;
13697
14060
  const toPascalCase = (type) => `${type[0].toUpperCase()}${type.slice(1)}`;
13698
14061
  let i = 0;
@@ -14190,7 +14553,9 @@ function createRoot(canvas) {
14190
14553
  camera: cameraOptions,
14191
14554
  onPointerMissed,
14192
14555
  onDragOverMissed,
14193
- onDropMissed
14556
+ onDropMissed,
14557
+ autoUpdateFrustum = true,
14558
+ occlusion = false
14194
14559
  } = props;
14195
14560
  let state = store.getState();
14196
14561
  const defaultGPUProps = {
@@ -14207,7 +14572,9 @@ function createRoot(canvas) {
14207
14572
  let renderer = state.internal.actualRenderer;
14208
14573
  if (!state.internal.actualRenderer) {
14209
14574
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14210
- await renderer.init();
14575
+ if (!renderer.hasInitialized?.()) {
14576
+ await renderer.init();
14577
+ }
14211
14578
  const backend = renderer.backend;
14212
14579
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14213
14580
  state.internal.actualRenderer = renderer;
@@ -14255,6 +14622,8 @@ function createRoot(canvas) {
14255
14622
  rootScene: scene,
14256
14623
  internal: { ...prev.internal, container: scene }
14257
14624
  }));
14625
+ const camera = state.camera;
14626
+ if (camera && !camera.parent) scene.add(camera);
14258
14627
  }
14259
14628
  if (events && !state.events.handlers) {
14260
14629
  state.set({ events: events(store) });
@@ -14282,6 +14651,10 @@ function createRoot(canvas) {
14282
14651
  if (!state.onPointerMissed) state.set({ onPointerMissed });
14283
14652
  if (!state.onDragOverMissed) state.set({ onDragOverMissed });
14284
14653
  if (!state.onDropMissed) state.set({ onDropMissed });
14654
+ if (state.autoUpdateFrustum !== autoUpdateFrustum) state.set({ autoUpdateFrustum });
14655
+ if (occlusion && !state.internal.occlusionEnabled) {
14656
+ enableOcclusion(store);
14657
+ }
14285
14658
  if (performance && !is.equ(performance, lastConfiguredProps.performance, shallowLoose)) {
14286
14659
  state.set((state2) => ({ performance: { ...state2.performance, ...performance } }));
14287
14660
  lastConfiguredProps.performance = performance;
@@ -14354,7 +14727,37 @@ function createRoot(canvas) {
14354
14727
  const rootId = state.internal.rootId;
14355
14728
  if (!rootId) {
14356
14729
  const newRootId = scheduler.generateRootId();
14357
- const unregisterRoot = scheduler.registerRoot(newRootId, () => store.getState());
14730
+ const unregisterRoot = scheduler.registerRoot(newRootId, {
14731
+ getState: () => store.getState(),
14732
+ onError: (err) => store.getState().setError(err)
14733
+ });
14734
+ const unregisterFrustum = scheduler.register(
14735
+ () => {
14736
+ const state2 = store.getState();
14737
+ if (state2.autoUpdateFrustum && state2.camera) {
14738
+ updateFrustum(state2.camera, state2.frustum);
14739
+ }
14740
+ },
14741
+ {
14742
+ id: `${newRootId}_frustum`,
14743
+ rootId: newRootId,
14744
+ phase: "preRender",
14745
+ system: true
14746
+ }
14747
+ );
14748
+ const unregisterVisibility = scheduler.register(
14749
+ () => {
14750
+ const state2 = store.getState();
14751
+ checkVisibility(state2);
14752
+ },
14753
+ {
14754
+ id: `${newRootId}_visibility`,
14755
+ rootId: newRootId,
14756
+ phase: "preRender",
14757
+ system: true,
14758
+ after: `${newRootId}_frustum`
14759
+ }
14760
+ );
14358
14761
  const unregisterRender = scheduler.register(
14359
14762
  () => {
14360
14763
  const state2 = store.getState();
@@ -14382,6 +14785,8 @@ function createRoot(canvas) {
14382
14785
  rootId: newRootId,
14383
14786
  unregisterRoot: () => {
14384
14787
  unregisterRoot();
14788
+ unregisterFrustum();
14789
+ unregisterVisibility();
14385
14790
  unregisterRender();
14386
14791
  },
14387
14792
  scheduler
@@ -14439,6 +14844,7 @@ function unmountComponentAtNode(canvas, callback) {
14439
14844
  const unregisterRoot = state.internal.unregisterRoot;
14440
14845
  if (unregisterRoot) unregisterRoot();
14441
14846
  state.events.disconnect?.();
14847
+ cleanupHelperGroup(root.store);
14442
14848
  renderer?.renderLists?.dispose?.();
14443
14849
  renderer?.forceContextLoss?.();
14444
14850
  if (renderer?.xr) state.xr.disconnect();
@@ -14618,7 +15024,22 @@ function CanvasImpl({
14618
15024
  effectActiveRef.current = true;
14619
15025
  const canvas = canvasRef.current;
14620
15026
  if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
14621
- if (!root.current) root.current = createRoot(canvas);
15027
+ if (!root.current) {
15028
+ root.current = createRoot(canvas);
15029
+ notifyAlpha({
15030
+ message: "React Three Fiber v10 is in ALPHA - expect breaking changes",
15031
+ link: "https://github.com/pmndrs/react-three-fiber/discussions"
15032
+ });
15033
+ const rootEntry = _roots.get(canvas);
15034
+ if (rootEntry?.store) {
15035
+ if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
15036
+ unsubscribeErrorRef.current = rootEntry.store.subscribe((state) => {
15037
+ if (state.error && effectActiveRef.current) {
15038
+ setError(state.error);
15039
+ }
15040
+ });
15041
+ }
15042
+ }
14622
15043
  async function run() {
14623
15044
  if (!effectActiveRef.current || !root.current) return;
14624
15045
  await root.current.configure({
@@ -14659,15 +15080,9 @@ function CanvasImpl({
14659
15080
  }
14660
15081
  });
14661
15082
  if (!effectActiveRef.current || !root.current) return;
14662
- const store = root.current.render(
15083
+ root.current.render(
14663
15084
  /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
14664
15085
  );
14665
- if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
14666
- unsubscribeErrorRef.current = store.subscribe((state) => {
14667
- if (state.error && effectActiveRef.current) {
14668
- setError(state.error);
14669
- }
14670
- });
14671
15086
  }
14672
15087
  run();
14673
15088
  }
@@ -14788,11 +15203,46 @@ function useCompareMemoize(value, deep) {
14788
15203
  const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
14789
15204
  function useUniforms(creatorOrScope, scope) {
14790
15205
  const store = useStore();
15206
+ const removeUniforms2 = useCallback(
15207
+ (names, targetScope) => {
15208
+ const nameArray = Array.isArray(names) ? names : [names];
15209
+ store.setState((state) => {
15210
+ if (targetScope) {
15211
+ const currentScope = { ...state.uniforms[targetScope] };
15212
+ for (const name of nameArray) delete currentScope[name];
15213
+ return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
15214
+ }
15215
+ const uniforms2 = { ...state.uniforms };
15216
+ for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
15217
+ return { uniforms: uniforms2 };
15218
+ });
15219
+ },
15220
+ [store]
15221
+ );
15222
+ const clearUniforms = useCallback(
15223
+ (targetScope) => {
15224
+ store.setState((state) => {
15225
+ if (targetScope && targetScope !== "root") {
15226
+ const { [targetScope]: _, ...rest } = state.uniforms;
15227
+ return { uniforms: rest };
15228
+ }
15229
+ if (targetScope === "root") {
15230
+ const uniforms2 = {};
15231
+ for (const [key, value] of Object.entries(state.uniforms)) {
15232
+ if (!isUniformNode$1(value)) uniforms2[key] = value;
15233
+ }
15234
+ return { uniforms: uniforms2 };
15235
+ }
15236
+ return { uniforms: {} };
15237
+ });
15238
+ },
15239
+ [store]
15240
+ );
14791
15241
  const inputForMemoization = useMemo(() => {
14792
15242
  return is.fun(creatorOrScope) ? creatorOrScope(store.getState()) : creatorOrScope;
14793
15243
  }, [creatorOrScope, store]);
14794
15244
  const memoizedInput = useCompareMemoize(inputForMemoization);
14795
- return useMemo(() => {
15245
+ const uniforms = useMemo(() => {
14796
15246
  const state = store.getState();
14797
15247
  const set = store.setState;
14798
15248
  if (memoizedInput === void 0) {
@@ -14838,44 +15288,26 @@ function useUniforms(creatorOrScope, scope) {
14838
15288
  set((s) => ({
14839
15289
  uniforms: {
14840
15290
  ...s.uniforms,
14841
- [scope]: {
14842
- ...s.uniforms[scope],
14843
- ...result
14844
- }
15291
+ [scope]: { ...s.uniforms[scope], ...result }
14845
15292
  }
14846
15293
  }));
14847
15294
  } else {
14848
- set((s) => ({
14849
- uniforms: {
14850
- ...s.uniforms,
14851
- ...result
14852
- }
14853
- }));
15295
+ set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
14854
15296
  }
14855
15297
  }
14856
15298
  return result;
14857
15299
  }, [store, memoizedInput, scope]);
15300
+ return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
14858
15301
  }
14859
15302
  function removeUniforms(set, names, scope) {
14860
15303
  set((state) => {
14861
15304
  if (scope) {
14862
15305
  const currentScope = { ...state.uniforms[scope] };
14863
- for (const name of names) {
14864
- delete currentScope[name];
14865
- }
14866
- return {
14867
- uniforms: {
14868
- ...state.uniforms,
14869
- [scope]: currentScope
14870
- }
14871
- };
15306
+ for (const name of names) delete currentScope[name];
15307
+ return { uniforms: { ...state.uniforms, [scope]: currentScope } };
14872
15308
  }
14873
15309
  const uniforms = { ...state.uniforms };
14874
- for (const name of names) {
14875
- if (isUniformNode$1(uniforms[name])) {
14876
- delete uniforms[name];
14877
- }
14878
- }
15310
+ for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
14879
15311
  return { uniforms };
14880
15312
  });
14881
15313
  }
@@ -14889,9 +15321,7 @@ function clearRootUniforms(set) {
14889
15321
  set((state) => {
14890
15322
  const uniforms = {};
14891
15323
  for (const [key, value] of Object.entries(state.uniforms)) {
14892
- if (!isUniformNode$1(value)) {
14893
- uniforms[key] = value;
14894
- }
15324
+ if (!isUniformNode$1(value)) uniforms[key] = value;
14895
15325
  }
14896
15326
  return { uniforms };
14897
15327
  });
@@ -14966,7 +15396,42 @@ function useUniform(name, value) {
14966
15396
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
14967
15397
  function useNodes(creatorOrScope, scope) {
14968
15398
  const store = useStore();
14969
- return useMemo(() => {
15399
+ const removeNodes2 = useCallback(
15400
+ (names, targetScope) => {
15401
+ const nameArray = Array.isArray(names) ? names : [names];
15402
+ store.setState((state) => {
15403
+ if (targetScope) {
15404
+ const currentScope = { ...state.nodes[targetScope] };
15405
+ for (const name of nameArray) delete currentScope[name];
15406
+ return { nodes: { ...state.nodes, [targetScope]: currentScope } };
15407
+ }
15408
+ const nodes2 = { ...state.nodes };
15409
+ for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
15410
+ return { nodes: nodes2 };
15411
+ });
15412
+ },
15413
+ [store]
15414
+ );
15415
+ const clearNodes = useCallback(
15416
+ (targetScope) => {
15417
+ store.setState((state) => {
15418
+ if (targetScope && targetScope !== "root") {
15419
+ const { [targetScope]: _, ...rest } = state.nodes;
15420
+ return { nodes: rest };
15421
+ }
15422
+ if (targetScope === "root") {
15423
+ const nodes2 = {};
15424
+ for (const [key, value] of Object.entries(state.nodes)) {
15425
+ if (!isTSLNode(value)) nodes2[key] = value;
15426
+ }
15427
+ return { nodes: nodes2 };
15428
+ }
15429
+ return { nodes: {} };
15430
+ });
15431
+ },
15432
+ [store]
15433
+ );
15434
+ const nodes = useMemo(() => {
14970
15435
  const state = store.getState();
14971
15436
  const set = store.setState;
14972
15437
  if (creatorOrScope === void 0) {
@@ -14974,9 +15439,7 @@ function useNodes(creatorOrScope, scope) {
14974
15439
  }
14975
15440
  if (typeof creatorOrScope === "string") {
14976
15441
  const scopeData = state.nodes[creatorOrScope];
14977
- if (scopeData && !isTSLNode(scopeData)) {
14978
- return scopeData;
14979
- }
15442
+ if (scopeData && !isTSLNode(scopeData)) return scopeData;
14980
15443
  return {};
14981
15444
  }
14982
15445
  const creator = creatorOrScope;
@@ -14989,9 +15452,7 @@ function useNodes(creatorOrScope, scope) {
14989
15452
  if (currentScope[name]) {
14990
15453
  result[name] = currentScope[name];
14991
15454
  } else {
14992
- if (typeof node.label === "function") {
14993
- node.setName(`${scope}.${name}`);
14994
- }
15455
+ if (typeof node.label === "function") node.setName(`${scope}.${name}`);
14995
15456
  result[name] = node;
14996
15457
  hasNewNodes = true;
14997
15458
  }
@@ -15000,10 +15461,7 @@ function useNodes(creatorOrScope, scope) {
15000
15461
  set((s) => ({
15001
15462
  nodes: {
15002
15463
  ...s.nodes,
15003
- [scope]: {
15004
- ...s.nodes[scope],
15005
- ...result
15006
- }
15464
+ [scope]: { ...s.nodes[scope], ...result }
15007
15465
  }
15008
15466
  }));
15009
15467
  }
@@ -15014,44 +15472,27 @@ function useNodes(creatorOrScope, scope) {
15014
15472
  if (existing && isTSLNode(existing)) {
15015
15473
  result[name] = existing;
15016
15474
  } else {
15017
- if (typeof node.label === "function") {
15018
- node.setName(name);
15019
- }
15475
+ if (typeof node.label === "function") node.setName(name);
15020
15476
  result[name] = node;
15021
15477
  hasNewNodes = true;
15022
15478
  }
15023
15479
  }
15024
15480
  if (hasNewNodes) {
15025
- set((s) => ({
15026
- nodes: {
15027
- ...s.nodes,
15028
- ...result
15029
- }
15030
- }));
15481
+ set((s) => ({ nodes: { ...s.nodes, ...result } }));
15031
15482
  }
15032
15483
  return result;
15033
15484
  }, [store, typeof creatorOrScope === "string" ? creatorOrScope : scope]);
15485
+ return { ...nodes, removeNodes: removeNodes2, clearNodes };
15034
15486
  }
15035
15487
  function removeNodes(set, names, scope) {
15036
15488
  set((state) => {
15037
15489
  if (scope) {
15038
15490
  const currentScope = { ...state.nodes[scope] };
15039
- for (const name of names) {
15040
- delete currentScope[name];
15041
- }
15042
- return {
15043
- nodes: {
15044
- ...state.nodes,
15045
- [scope]: currentScope
15046
- }
15047
- };
15491
+ for (const name of names) delete currentScope[name];
15492
+ return { nodes: { ...state.nodes, [scope]: currentScope } };
15048
15493
  }
15049
15494
  const nodes = { ...state.nodes };
15050
- for (const name of names) {
15051
- if (isTSLNode(nodes[name])) {
15052
- delete nodes[name];
15053
- }
15054
- }
15495
+ for (const name of names) if (isTSLNode(nodes[name])) delete nodes[name];
15055
15496
  return { nodes };
15056
15497
  });
15057
15498
  }
@@ -15065,9 +15506,7 @@ function clearRootNodes(set) {
15065
15506
  set((state) => {
15066
15507
  const nodes = {};
15067
15508
  for (const [key, value] of Object.entries(state.nodes)) {
15068
- if (!isTSLNode(value)) {
15069
- nodes[key] = value;
15070
- }
15509
+ if (!isTSLNode(value)) nodes[key] = value;
15071
15510
  }
15072
15511
  return { nodes };
15073
15512
  });
@@ -15174,4 +15613,4 @@ function usePostProcessing(mainCB, setupCB) {
15174
15613
 
15175
15614
  extend(THREE);
15176
15615
 
15177
- export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };
15616
+ export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };