@react-three/fiber 10.0.0-canary.8f5b4d3 → 10.0.0-canary.979dc2d

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,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
- const Inspector_js = require('three/addons/inspector/Inspector.js');
5
4
  const jsxRuntime = require('react/jsx-runtime');
6
5
  const React = require('react');
7
6
  const useMeasure = require('react-use-measure');
@@ -54,6 +53,10 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
54
53
 
55
54
  const R3F_BUILD_LEGACY = false;
56
55
  const R3F_BUILD_WEBGPU = true;
56
+ async function loadInspector() {
57
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
58
+ return Inspector;
59
+ }
57
60
  const WebGLRenderer = class WebGLRenderer2 {
58
61
  constructor() {
59
62
  throw new Error(
@@ -67,13 +70,13 @@ const WebGLCubeRenderTarget = null;
67
70
  const THREE = /*#__PURE__*/_mergeNamespaces({
68
71
  __proto__: null,
69
72
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
70
- Inspector: Inspector_js.Inspector,
71
73
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
74
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
75
  RenderTargetCompat: webgpu.RenderTarget,
74
76
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
75
77
  WebGLRenderTarget: WebGLRenderTarget,
76
- WebGLRenderer: WebGLRenderer
78
+ WebGLRenderer: WebGLRenderer,
79
+ loadInspector: loadInspector
77
80
  }, [webgpu__namespace]);
78
81
 
79
82
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -932,7 +935,7 @@ const createStore = (invalidate, advance) => {
932
935
  size: newSize,
933
936
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
934
937
  }));
935
- scheduler.getScheduler().invalidate();
938
+ get().invalidate();
936
939
  }
937
940
  }
938
941
  return;
@@ -947,15 +950,16 @@ const createStore = (invalidate, advance) => {
947
950
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
948
951
  _sizeImperative: true
949
952
  }));
950
- scheduler.getScheduler().invalidate();
953
+ get().invalidate();
951
954
  },
952
955
  setDpr: (dpr) => set((state2) => {
953
956
  const resolved = calculateDpr(dpr);
954
957
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
955
958
  }),
956
959
  setFrameloop: (frameloop = "always") => {
960
+ const rootId = get().internal.rootId;
961
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
957
962
  set(() => ({ frameloop }));
958
- scheduler.getScheduler().frameloop = frameloop;
959
963
  },
960
964
  setError: (error) => set(() => ({ error })),
961
965
  error: null,
@@ -1071,17 +1075,11 @@ const createStore = (invalidate, advance) => {
1071
1075
  oldSize = size;
1072
1076
  oldDpr = viewport.dpr;
1073
1077
  updateCamera(camera, size);
1074
- if (internal.isSecondary && canvasTarget) {
1075
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1076
- canvasTarget.setSize(size.width, size.height, false);
1077
- } else {
1078
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1079
- actualRenderer.setSize(size.width, size.height, false);
1080
- if (canvasTarget) {
1081
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1082
- canvasTarget.setSize(size.width, size.height, false);
1083
- }
1084
- }
1078
+ const resizeTarget = canvasTarget ?? actualRenderer;
1079
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1080
+ resizeTarget.setSize(size.width, size.height, false);
1081
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1082
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1085
1083
  }
1086
1084
  if (camera !== oldCamera) {
1087
1085
  oldCamera = camera;
@@ -1143,7 +1141,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1143
1141
  const results = cacheKeys.map(
1144
1142
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1145
1143
  );
1146
- return Array.isArray(input) ? results : results[0];
1144
+ const stableRef = React.useRef(null);
1145
+ const previous = stableRef.current;
1146
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1147
+ stableRef.current = stable;
1148
+ return Array.isArray(input) ? stable : stable[0];
1147
1149
  }
1148
1150
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1149
1151
  const inputs = Array.isArray(input) ? input : [input];
@@ -1158,7 +1160,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1158
1160
  };
1159
1161
  useLoader.loader = getLoader$1;
1160
1162
 
1161
- function useFrame(callback, priorityOrOptions) {
1163
+ function useFrame$1(callback, priorityOrOptions) {
1162
1164
  const store = React__namespace.useContext(context);
1163
1165
  const isInsideCanvas = store !== null;
1164
1166
  const scheduler$1 = scheduler.getScheduler();
@@ -1263,6 +1265,13 @@ function useFrame(callback, priorityOrOptions) {
1263
1265
  * Use for controlling the entire frame loop, adding phases, etc.
1264
1266
  */
1265
1267
  scheduler: scheduler2,
1268
+ /**
1269
+ * The root that currently owns this job. Resolve on access because ambient
1270
+ * jobs can be adopted by a Canvas after the controls object is created.
1271
+ */
1272
+ get rootId() {
1273
+ return scheduler.getScheduler().getJobRootId(id);
1274
+ },
1266
1275
  /**
1267
1276
  * Manually step this job only.
1268
1277
  * Bypasses FPS limiting - always runs.
@@ -1279,6 +1288,14 @@ function useFrame(callback, priorityOrOptions) {
1279
1288
  stepAll: (timestamp) => {
1280
1289
  scheduler.getScheduler().step(timestamp);
1281
1290
  },
1291
+ /**
1292
+ * Request frames for this job's owning root without waking sibling roots.
1293
+ */
1294
+ invalidate: (frames, stackFrames) => {
1295
+ const scheduler3 = scheduler.getScheduler();
1296
+ const rootId = scheduler3.getJobRootId(id);
1297
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1298
+ },
1282
1299
  /**
1283
1300
  * Pause this job (set enabled=false).
1284
1301
  * Job remains registered but won't run.
@@ -1308,6 +1325,11 @@ function getUrls(input) {
1308
1325
  if (Array.isArray(input)) return input;
1309
1326
  return Object.values(input);
1310
1327
  }
1328
+ function inputSignature(input) {
1329
+ if (typeof input === "string") return input;
1330
+ if (Array.isArray(input)) return input.join("\0");
1331
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1332
+ }
1311
1333
  function allUrlsCached(urls, textureCache) {
1312
1334
  return urls.every((url) => textureCache.has(url));
1313
1335
  }
@@ -1326,25 +1348,26 @@ function buildFromCache(input, textureCache) {
1326
1348
  }
1327
1349
  }
1328
1350
  function useTexture(input, optionsOrOnLoad) {
1329
- const renderer = useThree((state) => state.internal.actualRenderer);
1351
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1330
1352
  const store = useStore();
1331
1353
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1332
1354
  const { onLoad, cache = true } = options;
1333
1355
  const onLoadRef = React.useRef(onLoad);
1334
1356
  onLoadRef.current = onLoad;
1335
1357
  const onLoadCalledForRef = React.useRef(null);
1336
- const urls = React.useMemo(() => getUrls(input), [input]);
1358
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1359
+ const stableInput = React.useMemo(() => input, [inputKey]);
1360
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1337
1361
  const cachedResult = React.useMemo(() => {
1338
1362
  if (!cache) return null;
1339
1363
  const textures = store.getState().textures;
1340
1364
  if (!allUrlsCached(urls, textures)) return null;
1341
- return buildFromCache(input, textures);
1342
- }, [cache, urls, input, store]);
1365
+ return buildFromCache(stableInput, textures);
1366
+ }, [cache, urls, stableInput, store]);
1343
1367
  const loadedTextures = useLoader(
1344
1368
  webgpu.TextureLoader,
1345
- IsObject(input) ? Object.values(input) : input
1369
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1346
1370
  );
1347
- const inputKey = urls.join("\0");
1348
1371
  React.useLayoutEffect(() => {
1349
1372
  if (cachedResult) return;
1350
1373
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1371,28 +1394,28 @@ function useTexture(input, optionsOrOnLoad) {
1371
1394
  }, [renderer, loadedTextures, cachedResult]);
1372
1395
  const mappedTextures = React.useMemo(() => {
1373
1396
  if (cachedResult) return cachedResult;
1374
- if (IsObject(input)) {
1397
+ if (IsObject(stableInput)) {
1375
1398
  const keyed = {};
1376
1399
  const textureArray = loadedTextures;
1377
1400
  let i = 0;
1378
- for (const key in input) keyed[key] = textureArray[i++];
1401
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1379
1402
  return keyed;
1380
1403
  } else {
1381
1404
  return loadedTextures;
1382
1405
  }
1383
- }, [input, loadedTextures, cachedResult]);
1406
+ }, [stableInput, loadedTextures, cachedResult]);
1384
1407
  React.useEffect(() => {
1385
1408
  if (!cache) return;
1386
1409
  const urlTextureMap = [];
1387
- if (typeof input === "string") {
1388
- urlTextureMap.push([input, mappedTextures]);
1389
- } else if (Array.isArray(input)) {
1410
+ if (typeof stableInput === "string") {
1411
+ urlTextureMap.push([stableInput, mappedTextures]);
1412
+ } else if (Array.isArray(stableInput)) {
1390
1413
  const textureArray = mappedTextures;
1391
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1392
- } else if (IsObject(input)) {
1414
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1415
+ } else if (IsObject(stableInput)) {
1393
1416
  const textureRecord = mappedTextures;
1394
- for (const key in input) {
1395
- const url = input[key];
1417
+ for (const key in stableInput) {
1418
+ const url = stableInput[key];
1396
1419
  urlTextureMap.push([url, textureRecord[key]]);
1397
1420
  }
1398
1421
  }
@@ -1421,7 +1444,7 @@ function useTexture(input, optionsOrOnLoad) {
1421
1444
  }
1422
1445
  return { _textureRefs: refs };
1423
1446
  });
1424
- }, [cache, input, mappedTextures, store]);
1447
+ }, [cache, stableInput, mappedTextures, store]);
1425
1448
  return mappedTextures;
1426
1449
  }
1427
1450
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1492,13 +1515,13 @@ function useTextures(selector) {
1492
1515
  };
1493
1516
  }, [store]);
1494
1517
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1495
- const selected = useThree(subscribe);
1518
+ const selected = useThree$1(subscribe);
1496
1519
  return selector ? selected : registry;
1497
1520
  }
1498
1521
 
1499
1522
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1500
- const isLegacy = useThree((s) => s.isLegacy);
1501
- const size = useThree((s) => s.size);
1523
+ const isLegacy = useThree$1((s) => s.isLegacy);
1524
+ const size = useThree$1((s) => s.size);
1502
1525
  let width;
1503
1526
  let height;
1504
1527
  let opts;
@@ -1555,7 +1578,7 @@ function useEnvironment({
1555
1578
  const { extension, isCubemap } = getExtension(files);
1556
1579
  const loader = getLoader(extension);
1557
1580
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1558
- const renderer = useThree((state) => state.renderer);
1581
+ const renderer = useThree$1((state) => state.renderer);
1559
1582
  React.useLayoutEffect(() => {
1560
1583
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1561
1584
  function clearGainmapTexture() {
@@ -1648,7 +1671,7 @@ function useStore() {
1648
1671
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1649
1672
  return store;
1650
1673
  }
1651
- function useThree(selector = (state) => state, equalityFn) {
1674
+ function useThree$1(selector = (state) => state, equalityFn) {
1652
1675
  return useStore()(selector, equalityFn);
1653
1676
  }
1654
1677
  function useInstanceHandle(ref) {
@@ -1690,13 +1713,19 @@ function addTail(callback) {
1690
1713
  return scheduler.getScheduler().onIdle(callback);
1691
1714
  }
1692
1715
  function invalidate(state, frames = 1, stackFrames = false) {
1693
- scheduler.getScheduler().invalidate(frames, stackFrames);
1716
+ const rootId = state?.internal?.rootId;
1717
+ const scheduler$1 = scheduler.getScheduler();
1718
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1719
+ else scheduler$1.invalidate(frames, stackFrames);
1694
1720
  }
1695
- function advance(timestamp) {
1696
- scheduler.getScheduler().step(timestamp);
1721
+ function advance(timestamp, runGlobalEffects, state) {
1722
+ const rootId = state?.internal?.rootId;
1723
+ const scheduler$1 = scheduler.getScheduler();
1724
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1725
+ else scheduler$1.step(timestamp);
1697
1726
  }
1698
1727
 
1699
- const version = "10.0.0-alpha.3";
1728
+ const version = "10.0.0-alpha.4";
1700
1729
  const packageData = {
1701
1730
  version: version};
1702
1731
 
@@ -12979,7 +13008,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12979
13008
  const captureData = pointerState.captured.get(obj);
12980
13009
  if (captureData) {
12981
13010
  pointerState.captured.delete(obj);
12982
- captureData.target.releasePointerCapture(pointerId);
13011
+ try {
13012
+ captureData.target.releasePointerCapture?.(pointerId);
13013
+ } catch {
13014
+ }
12983
13015
  }
12984
13016
  }
12985
13017
  function swapInteractivity(store, object, newObject) {
@@ -13073,7 +13105,9 @@ function createEvents(store) {
13073
13105
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13074
13106
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13075
13107
  }
13076
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13108
+ if (!state2.raycaster.camera) return [];
13109
+ obj.updateWorldMatrix(true, true);
13110
+ return state2.raycaster.intersectObject(obj, true);
13077
13111
  }
13078
13112
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13079
13113
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14207,9 +14241,8 @@ function createRoot(canvas) {
14207
14241
  if (!renderer.hasInitialized?.()) {
14208
14242
  const size2 = computeInitialSize(canvas, propsSize);
14209
14243
  if (size2.width > 0 && size2.height > 0) {
14210
- const pixelRatio = calculateDpr(dpr);
14211
- canvas.width = size2.width * pixelRatio;
14212
- canvas.height = size2.height * pixelRatio;
14244
+ renderer.setPixelRatio(calculateDpr(dpr));
14245
+ renderer.setSize(size2.width, size2.height, false);
14213
14246
  }
14214
14247
  await renderer.init();
14215
14248
  }
@@ -14218,7 +14251,7 @@ function createRoot(canvas) {
14218
14251
  state.internal.actualRenderer = renderer;
14219
14252
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14220
14253
  if (canvasId && !state.internal.isSecondary) {
14221
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14254
+ const canvasTarget = renderer.getCanvasTarget?.();
14222
14255
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14223
14256
  state.set((prev) => ({
14224
14257
  internal: {
@@ -14332,7 +14365,7 @@ function createRoot(canvas) {
14332
14365
  const handleXRFrame = (timestamp, _frame) => {
14333
14366
  const state2 = store.getState();
14334
14367
  if (state2.frameloop === "never") return;
14335
- advance(timestamp);
14368
+ advance(timestamp, true, state2);
14336
14369
  };
14337
14370
  const actualRenderer = state.internal.actualRenderer;
14338
14371
  const handleSessionChange = () => {
@@ -14340,7 +14373,7 @@ function createRoot(canvas) {
14340
14373
  const renderer2 = state2.internal.actualRenderer;
14341
14374
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14342
14375
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14343
- if (!renderer2.xr.isPresenting) invalidate();
14376
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14344
14377
  };
14345
14378
  const xr = {
14346
14379
  connect() {
@@ -14422,14 +14455,23 @@ function createRoot(canvas) {
14422
14455
  const newRootId = canvasId || scheduler$1.generateRootId();
14423
14456
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14424
14457
  getState: () => store.getState(),
14425
- onError: (err) => store.getState().setError(err)
14458
+ onError: (err) => store.getState().setError(err),
14459
+ frameloop: store.getState().frameloop,
14460
+ before: schedulerConfig?.before,
14461
+ after: schedulerConfig?.after,
14462
+ order: schedulerConfig?.order
14426
14463
  });
14427
14464
  const unregisterCanvasTarget = scheduler$1.register(
14428
14465
  () => {
14429
14466
  const state2 = store.getState();
14430
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14467
+ const canvasTarget = state2.internal.canvasTarget;
14468
+ if (canvasTarget) {
14431
14469
  const renderer2 = state2.internal.actualRenderer;
14432
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14470
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14471
+ if (state2.internal.canvasTargetSizeDirty) {
14472
+ state2.internal.canvasTargetSizeDirty = false;
14473
+ renderer2.backend?.updateSize?.();
14474
+ }
14433
14475
  }
14434
14476
  },
14435
14477
  {
@@ -14498,8 +14540,7 @@ function createRoot(canvas) {
14498
14540
  phase: "render",
14499
14541
  system: true,
14500
14542
  // Internal flag: this is a system job, not user-controlled
14501
- // Apply scheduler config for render ordering and rate limiting
14502
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14543
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14503
14544
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14504
14545
  }
14505
14546
  );
@@ -14518,8 +14559,21 @@ function createRoot(canvas) {
14518
14559
  scheduler: scheduler$1
14519
14560
  }
14520
14561
  }));
14562
+ } else {
14563
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14564
+ if (constraintsChanged) {
14565
+ scheduler$1.setRootConstraints(rootId, {
14566
+ before: schedulerConfig?.before,
14567
+ after: schedulerConfig?.after
14568
+ });
14569
+ }
14570
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14571
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14572
+ }
14521
14573
  }
14522
- scheduler$1.frameloop = frameloop;
14574
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14575
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14576
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14523
14577
  onCreated = onCreatedCallback;
14524
14578
  configured = true;
14525
14579
  resolve();
@@ -14561,18 +14615,23 @@ function unmountComponentAtNode(canvas, callback) {
14561
14615
  const fiber = root?.fiber;
14562
14616
  if (fiber) {
14563
14617
  const state = root?.store.getState();
14564
- if (state) state.internal.active = false;
14618
+ if (state) {
14619
+ state.internal.active = false;
14620
+ const unregisterRoot = state.internal.unregisterRoot;
14621
+ if (unregisterRoot) {
14622
+ unregisterRoot();
14623
+ state.internal.unregisterRoot = void 0;
14624
+ }
14625
+ }
14565
14626
  reconciler.updateContainer(null, fiber, null, () => {
14566
14627
  if (state) {
14567
14628
  setTimeout(() => {
14568
14629
  try {
14569
14630
  const renderer = state.internal.actualRenderer;
14570
- const unregisterRoot = state.internal.unregisterRoot;
14571
- if (unregisterRoot) unregisterRoot();
14572
14631
  const unregisterPrimary = state.internal.unregisterPrimary;
14573
14632
  if (unregisterPrimary) unregisterPrimary();
14574
14633
  const canvasTarget = state.internal.canvasTarget;
14575
- if (canvasTarget?.dispose) canvasTarget.dispose();
14634
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14576
14635
  state.events.disconnect?.();
14577
14636
  cleanupHelperGroup(root.store);
14578
14637
  if (state.isLegacy && renderer) {
@@ -14734,7 +14793,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14734
14793
  };
14735
14794
  }
14736
14795
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14737
- const defaultScene = useThree((state) => state.scene);
14796
+ const defaultScene = useThree$1((state) => state.scene);
14738
14797
  React__namespace.useLayoutEffect(() => {
14739
14798
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14740
14799
  });
@@ -14752,7 +14811,7 @@ function EnvironmentCube({
14752
14811
  ...rest
14753
14812
  }) {
14754
14813
  const texture = useEnvironment(rest);
14755
- const defaultScene = useThree((state) => state.scene);
14814
+ const defaultScene = useThree$1((state) => state.scene);
14756
14815
  React__namespace.useLayoutEffect(() => {
14757
14816
  return setEnvProps(background, scene, defaultScene, texture, {
14758
14817
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14789,9 +14848,9 @@ function EnvironmentPortal({
14789
14848
  preset = void 0,
14790
14849
  extensions
14791
14850
  }) {
14792
- const renderer = useThree((state) => state.renderer);
14793
- const isLegacy = useThree((state) => state.isLegacy);
14794
- const defaultScene = useThree((state) => state.scene);
14851
+ const renderer = useThree$1((state) => state.renderer);
14852
+ const isLegacy = useThree$1((state) => state.isLegacy);
14853
+ const defaultScene = useThree$1((state) => state.scene);
14795
14854
  const camera = React__namespace.useRef(null);
14796
14855
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
14797
14856
  const fbo = React__namespace.useMemo(() => {
@@ -14835,7 +14894,7 @@ function EnvironmentPortal({
14835
14894
  environmentRotation
14836
14895
  ]);
14837
14896
  let count = 1;
14838
- useFrame(() => {
14897
+ useFrame$1(() => {
14839
14898
  if (frames === Infinity || count < frames) {
14840
14899
  const autoClear = renderer.autoClear;
14841
14900
  renderer.autoClear = true;
@@ -14875,7 +14934,7 @@ function EnvironmentGround(props) {
14875
14934
  ] });
14876
14935
  }
14877
14936
  function EnvironmentColor({ color, scene }) {
14878
- const defaultScene = useThree((state) => state.scene);
14937
+ const defaultScene = useThree$1((state) => state.scene);
14879
14938
  React__namespace.useLayoutEffect(() => {
14880
14939
  if (color === void 0) return;
14881
14940
  const target = resolveScene(scene || defaultScene);
@@ -15113,9 +15172,9 @@ function CanvasImpl({
15113
15172
  React__namespace.useMemo(() => extend(THREE), []);
15114
15173
  const Bridge = useBridge();
15115
15174
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15116
- const hasInitialSizeRef = React__namespace.useRef(false);
15175
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15117
15176
  const measureConfig = React__namespace.useMemo(() => {
15118
- if (!hasInitialSizeRef.current) {
15177
+ if (!hasInitialSize) {
15119
15178
  return {
15120
15179
  ...resize,
15121
15180
  scroll: resize?.scroll ?? true,
@@ -15127,7 +15186,7 @@ function CanvasImpl({
15127
15186
  debounce: { scroll: 50, resize: 0 },
15128
15187
  ...resize
15129
15188
  };
15130
- }, [resize, hasInitialSizeRef.current]);
15189
+ }, [hasInitialSize, resize]);
15131
15190
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15132
15191
  const effectiveSize = React__namespace.useMemo(() => {
15133
15192
  let w = width ?? containerRect.width;
@@ -15143,9 +15202,7 @@ function CanvasImpl({
15143
15202
  left: containerRect.left
15144
15203
  };
15145
15204
  }, [width, height, containerRect, forceEven]);
15146
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15147
- hasInitialSizeRef.current = true;
15148
- }
15205
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15149
15206
  const canvasRef = React__namespace.useRef(null);
15150
15207
  const divRef = React__namespace.useRef(null);
15151
15208
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15259,6 +15316,7 @@ function CanvasImpl({
15259
15316
  const canvas = canvasRef.current;
15260
15317
  if (canvas) {
15261
15318
  return () => {
15319
+ if (canvas.isConnected) return;
15262
15320
  unmountComponentAtNode(canvas);
15263
15321
  root.current = null;
15264
15322
  };
@@ -15469,6 +15527,11 @@ function createTextureOperations(set) {
15469
15527
  removeMultiple: (keys) => removeTextures(set, keys)
15470
15528
  };
15471
15529
  }
15530
+ function scopedNodeName(scope, name) {
15531
+ const raw = scope ? `${scope}_${name}` : name;
15532
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15533
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15534
+ }
15472
15535
  function extractTSLValue(value) {
15473
15536
  if (value === null || value === void 0) return value;
15474
15537
  if (typeof value !== "object") return value;
@@ -15667,8 +15730,7 @@ function createUniform(inName, node, scope) {
15667
15730
  const inValue = vectorize(node);
15668
15731
  const newUniform = tsl.uniform(inValue);
15669
15732
  if (typeof newUniform.setName === "function") {
15670
- const name = scope ? `${scope}_${inName}` : inName;
15671
- newUniform.setName(name);
15733
+ newUniform.setName(scopedNodeName(scope, inName));
15672
15734
  }
15673
15735
  return newUniform;
15674
15736
  }
@@ -15767,7 +15829,7 @@ function useNodes(creatorOrScope, scope) {
15767
15829
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15768
15830
  },
15769
15831
  prepare: (name, node) => {
15770
- node.setName?.(scope ? `${scope}.${name}` : name);
15832
+ node.setName?.(scopedNodeName(scope, name));
15771
15833
  return node;
15772
15834
  }
15773
15835
  });
@@ -15876,7 +15938,7 @@ function useBuffers(creatorOrScope, scope) {
15876
15938
  },
15877
15939
  prepare: (name, buffer) => {
15878
15940
  if ("setName" in buffer && typeof buffer.setName === "function") {
15879
- buffer.setName(scope ? `${scope}.${name}` : name);
15941
+ buffer.setName(scopedNodeName(scope, name));
15880
15942
  }
15881
15943
  return buffer;
15882
15944
  }
@@ -15947,7 +16009,7 @@ function useGPUStorage(creatorOrScope, scope) {
15947
16009
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15948
16010
  },
15949
16011
  prepare: (name, storage) => {
15950
- const label = scope ? `${scope}.${name}` : name;
16012
+ const label = scopedNodeName(scope, name);
15951
16013
  if ("setName" in storage && typeof storage.setName === "function") {
15952
16014
  storage.setName(label);
15953
16015
  }
@@ -15978,9 +16040,10 @@ function rebuildAllStorage(store, scope) {
15978
16040
 
15979
16041
  function useRenderPipeline(mainCB, setupCB) {
15980
16042
  const store = useStore();
15981
- const { scene, camera, renderer, isLegacy } = useThree();
16043
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15982
16044
  const callbacksRanRef = React.useRef(false);
15983
16045
  const scenePassCacheRef = React.useRef(null);
16046
+ const forceScenePassRebuildRef = React.useRef(false);
15984
16047
  const mainCBRef = React.useRef(mainCB);
15985
16048
  const setupCBRef = React.useRef(setupCB);
15986
16049
  mainCBRef.current = mainCB;
@@ -15988,7 +16051,7 @@ function useRenderPipeline(mainCB, setupCB) {
15988
16051
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15989
16052
  React.useEffect(() => {
15990
16053
  callbacksRanRef.current = false;
15991
- scenePassCacheRef.current = null;
16054
+ forceScenePassRebuildRef.current = true;
15992
16055
  }, []);
15993
16056
  const clearPasses = React.useCallback(() => {
15994
16057
  store.setState({ passes: {} });
@@ -15998,12 +16061,14 @@ function useRenderPipeline(mainCB, setupCB) {
15998
16061
  renderPipeline: null,
15999
16062
  passes: {}
16000
16063
  });
16064
+ scenePassCacheRef.current?.scenePass.dispose();
16001
16065
  callbacksRanRef.current = false;
16002
16066
  scenePassCacheRef.current = null;
16067
+ forceScenePassRebuildRef.current = false;
16003
16068
  }, [store]);
16004
16069
  const rebuild = React.useCallback(() => {
16005
16070
  callbacksRanRef.current = false;
16006
- scenePassCacheRef.current = null;
16071
+ forceScenePassRebuildRef.current = true;
16007
16072
  setRebuildVersion((v) => v + 1);
16008
16073
  }, []);
16009
16074
  React.useLayoutEffect(() => {
@@ -16011,6 +16076,9 @@ function useRenderPipeline(mainCB, setupCB) {
16011
16076
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16012
16077
  }
16013
16078
  if (!renderer || !scene || !camera) return;
16079
+ if (!("isWebGPURenderer" in renderer)) {
16080
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16081
+ }
16014
16082
  const state = store.getState();
16015
16083
  const set = store.setState;
16016
16084
  try {
@@ -16021,21 +16089,33 @@ function useRenderPipeline(mainCB, setupCB) {
16021
16089
  pipeline = new webgpu.RenderPipeline(renderer);
16022
16090
  justCreatedPipeline = true;
16023
16091
  }
16024
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16092
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16093
+ let outgoingScenePass = null;
16025
16094
  let scenePass;
16026
16095
  if (cacheValid) {
16027
16096
  scenePass = scenePassCacheRef.current.scenePass;
16028
16097
  } else {
16098
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16029
16099
  scenePass = tsl.pass(scene, camera);
16030
16100
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16101
+ forceScenePassRebuildRef.current = false;
16031
16102
  }
16032
16103
  currentPasses.scenePass = scenePass;
16033
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16104
+ let outputNodeChanged = false;
16105
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16106
+ pipeline.outputNode = scenePass;
16107
+ outputNodeChanged = true;
16108
+ }
16034
16109
  set({ renderPipeline: pipeline, passes: currentPasses });
16035
16110
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16036
16111
  if (shouldRunCallbacks) {
16037
16112
  if (setupCBRef.current) {
16038
- const freshState = store.getState();
16113
+ const freshState = Object.assign({}, store.getState(), {
16114
+ renderPipeline: pipeline,
16115
+ passes: { ...currentPasses, scenePass },
16116
+ renderer,
16117
+ isLegacy: false
16118
+ });
16039
16119
  const setupResult = setupCBRef.current(freshState);
16040
16120
  if (setupResult && typeof setupResult === "object") {
16041
16121
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16043,33 +16123,37 @@ function useRenderPipeline(mainCB, setupCB) {
16043
16123
  }
16044
16124
  }
16045
16125
  if (mainCBRef.current) {
16046
- const freshState = store.getState();
16126
+ const freshState = Object.assign({}, store.getState(), {
16127
+ renderPipeline: pipeline,
16128
+ passes: { ...currentPasses, scenePass },
16129
+ renderer,
16130
+ isLegacy: false
16131
+ });
16047
16132
  const mainResult = mainCBRef.current(freshState);
16048
16133
  if (mainResult && typeof mainResult === "object") {
16049
16134
  currentPasses = { ...currentPasses, ...mainResult };
16050
16135
  set({ passes: currentPasses });
16051
16136
  }
16052
16137
  }
16138
+ outputNodeChanged = true;
16053
16139
  callbacksRanRef.current = true;
16054
16140
  }
16141
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16142
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16055
16143
  } catch (error) {
16056
16144
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16057
16145
  }
16058
16146
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16059
- const passes = useThree((s) => s.passes);
16060
- const renderPipeline = useThree((s) => s.renderPipeline);
16061
- return {
16062
- passes,
16063
- renderPipeline,
16064
- clearPasses,
16065
- reset,
16066
- rebuild,
16067
- // isReady indicates if RenderPipeline is configured and ready for rendering
16068
- isReady: renderPipeline !== null
16069
- };
16147
+ const passes = useThree$1((s) => s.passes);
16148
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16149
+ const actions = { passes, clearPasses, reset, rebuild };
16150
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16151
+ return { ...actions, isReady: false, renderPipeline: null };
16070
16152
  }
16071
16153
 
16072
16154
  extend(THREE);
16155
+ const useThree = useThree$1;
16156
+ const useFrame = useFrame$1;
16073
16157
 
16074
16158
  exports.Scheduler = scheduler.Scheduler;
16075
16159
  exports.getScheduler = scheduler.getScheduler;