@react-three/fiber 10.0.0-canary.c6846c3 → 10.0.0-canary.cdaab7c

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,10 @@ 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
+ internal.canvasTargetSizeDirty = true;
1085
1082
  }
1086
1083
  if (camera !== oldCamera) {
1087
1084
  oldCamera = camera;
@@ -1143,7 +1140,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1143
1140
  const results = cacheKeys.map(
1144
1141
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1145
1142
  );
1146
- return Array.isArray(input) ? results : results[0];
1143
+ const stableRef = React.useRef(null);
1144
+ const previous = stableRef.current;
1145
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1146
+ stableRef.current = stable;
1147
+ return Array.isArray(input) ? stable : stable[0];
1147
1148
  }
1148
1149
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1149
1150
  const inputs = Array.isArray(input) ? input : [input];
@@ -1158,7 +1159,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1158
1159
  };
1159
1160
  useLoader.loader = getLoader$1;
1160
1161
 
1161
- function useFrame(callback, priorityOrOptions) {
1162
+ function useFrame$1(callback, priorityOrOptions) {
1162
1163
  const store = React__namespace.useContext(context);
1163
1164
  const isInsideCanvas = store !== null;
1164
1165
  const scheduler$1 = scheduler.getScheduler();
@@ -1263,6 +1264,13 @@ function useFrame(callback, priorityOrOptions) {
1263
1264
  * Use for controlling the entire frame loop, adding phases, etc.
1264
1265
  */
1265
1266
  scheduler: scheduler2,
1267
+ /**
1268
+ * The root that currently owns this job. Resolve on access because ambient
1269
+ * jobs can be adopted by a Canvas after the controls object is created.
1270
+ */
1271
+ get rootId() {
1272
+ return scheduler.getScheduler().getJobRootId(id);
1273
+ },
1266
1274
  /**
1267
1275
  * Manually step this job only.
1268
1276
  * Bypasses FPS limiting - always runs.
@@ -1279,6 +1287,14 @@ function useFrame(callback, priorityOrOptions) {
1279
1287
  stepAll: (timestamp) => {
1280
1288
  scheduler.getScheduler().step(timestamp);
1281
1289
  },
1290
+ /**
1291
+ * Request frames for this job's owning root without waking sibling roots.
1292
+ */
1293
+ invalidate: (frames, stackFrames) => {
1294
+ const scheduler3 = scheduler.getScheduler();
1295
+ const rootId = scheduler3.getJobRootId(id);
1296
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1297
+ },
1282
1298
  /**
1283
1299
  * Pause this job (set enabled=false).
1284
1300
  * Job remains registered but won't run.
@@ -1308,6 +1324,11 @@ function getUrls(input) {
1308
1324
  if (Array.isArray(input)) return input;
1309
1325
  return Object.values(input);
1310
1326
  }
1327
+ function inputSignature(input) {
1328
+ if (typeof input === "string") return input;
1329
+ if (Array.isArray(input)) return input.join("\0");
1330
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1331
+ }
1311
1332
  function allUrlsCached(urls, textureCache) {
1312
1333
  return urls.every((url) => textureCache.has(url));
1313
1334
  }
@@ -1326,25 +1347,26 @@ function buildFromCache(input, textureCache) {
1326
1347
  }
1327
1348
  }
1328
1349
  function useTexture(input, optionsOrOnLoad) {
1329
- const renderer = useThree((state) => state.internal.actualRenderer);
1350
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1330
1351
  const store = useStore();
1331
1352
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1332
1353
  const { onLoad, cache = true } = options;
1333
1354
  const onLoadRef = React.useRef(onLoad);
1334
1355
  onLoadRef.current = onLoad;
1335
1356
  const onLoadCalledForRef = React.useRef(null);
1336
- const urls = React.useMemo(() => getUrls(input), [input]);
1357
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1358
+ const stableInput = React.useMemo(() => input, [inputKey]);
1359
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1337
1360
  const cachedResult = React.useMemo(() => {
1338
1361
  if (!cache) return null;
1339
1362
  const textures = store.getState().textures;
1340
1363
  if (!allUrlsCached(urls, textures)) return null;
1341
- return buildFromCache(input, textures);
1342
- }, [cache, urls, input, store]);
1364
+ return buildFromCache(stableInput, textures);
1365
+ }, [cache, urls, stableInput, store]);
1343
1366
  const loadedTextures = useLoader(
1344
1367
  webgpu.TextureLoader,
1345
- IsObject(input) ? Object.values(input) : input
1368
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1346
1369
  );
1347
- const inputKey = urls.join("\0");
1348
1370
  React.useLayoutEffect(() => {
1349
1371
  if (cachedResult) return;
1350
1372
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1371,28 +1393,28 @@ function useTexture(input, optionsOrOnLoad) {
1371
1393
  }, [renderer, loadedTextures, cachedResult]);
1372
1394
  const mappedTextures = React.useMemo(() => {
1373
1395
  if (cachedResult) return cachedResult;
1374
- if (IsObject(input)) {
1396
+ if (IsObject(stableInput)) {
1375
1397
  const keyed = {};
1376
1398
  const textureArray = loadedTextures;
1377
1399
  let i = 0;
1378
- for (const key in input) keyed[key] = textureArray[i++];
1400
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1379
1401
  return keyed;
1380
1402
  } else {
1381
1403
  return loadedTextures;
1382
1404
  }
1383
- }, [input, loadedTextures, cachedResult]);
1405
+ }, [stableInput, loadedTextures, cachedResult]);
1384
1406
  React.useEffect(() => {
1385
1407
  if (!cache) return;
1386
1408
  const urlTextureMap = [];
1387
- if (typeof input === "string") {
1388
- urlTextureMap.push([input, mappedTextures]);
1389
- } else if (Array.isArray(input)) {
1409
+ if (typeof stableInput === "string") {
1410
+ urlTextureMap.push([stableInput, mappedTextures]);
1411
+ } else if (Array.isArray(stableInput)) {
1390
1412
  const textureArray = mappedTextures;
1391
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1392
- } else if (IsObject(input)) {
1413
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1414
+ } else if (IsObject(stableInput)) {
1393
1415
  const textureRecord = mappedTextures;
1394
- for (const key in input) {
1395
- const url = input[key];
1416
+ for (const key in stableInput) {
1417
+ const url = stableInput[key];
1396
1418
  urlTextureMap.push([url, textureRecord[key]]);
1397
1419
  }
1398
1420
  }
@@ -1421,7 +1443,7 @@ function useTexture(input, optionsOrOnLoad) {
1421
1443
  }
1422
1444
  return { _textureRefs: refs };
1423
1445
  });
1424
- }, [cache, input, mappedTextures, store]);
1446
+ }, [cache, stableInput, mappedTextures, store]);
1425
1447
  return mappedTextures;
1426
1448
  }
1427
1449
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1492,13 +1514,13 @@ function useTextures(selector) {
1492
1514
  };
1493
1515
  }, [store]);
1494
1516
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1495
- const selected = useThree(subscribe);
1517
+ const selected = useThree$1(subscribe);
1496
1518
  return selector ? selected : registry;
1497
1519
  }
1498
1520
 
1499
1521
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1500
- const isLegacy = useThree((s) => s.isLegacy);
1501
- const size = useThree((s) => s.size);
1522
+ const isLegacy = useThree$1((s) => s.isLegacy);
1523
+ const size = useThree$1((s) => s.size);
1502
1524
  let width;
1503
1525
  let height;
1504
1526
  let opts;
@@ -1555,7 +1577,7 @@ function useEnvironment({
1555
1577
  const { extension, isCubemap } = getExtension(files);
1556
1578
  const loader = getLoader(extension);
1557
1579
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1558
- const renderer = useThree((state) => state.renderer);
1580
+ const renderer = useThree$1((state) => state.renderer);
1559
1581
  React.useLayoutEffect(() => {
1560
1582
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1561
1583
  function clearGainmapTexture() {
@@ -1648,7 +1670,7 @@ function useStore() {
1648
1670
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1649
1671
  return store;
1650
1672
  }
1651
- function useThree(selector = (state) => state, equalityFn) {
1673
+ function useThree$1(selector = (state) => state, equalityFn) {
1652
1674
  return useStore()(selector, equalityFn);
1653
1675
  }
1654
1676
  function useInstanceHandle(ref) {
@@ -1690,13 +1712,19 @@ function addTail(callback) {
1690
1712
  return scheduler.getScheduler().onIdle(callback);
1691
1713
  }
1692
1714
  function invalidate(state, frames = 1, stackFrames = false) {
1693
- scheduler.getScheduler().invalidate(frames, stackFrames);
1715
+ const rootId = state?.internal?.rootId;
1716
+ const scheduler$1 = scheduler.getScheduler();
1717
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1718
+ else scheduler$1.invalidate(frames, stackFrames);
1694
1719
  }
1695
- function advance(timestamp) {
1696
- scheduler.getScheduler().step(timestamp);
1720
+ function advance(timestamp, runGlobalEffects, state) {
1721
+ const rootId = state?.internal?.rootId;
1722
+ const scheduler$1 = scheduler.getScheduler();
1723
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1724
+ else scheduler$1.step(timestamp);
1697
1725
  }
1698
1726
 
1699
- const version = "10.0.0-alpha.3";
1727
+ const version = "10.0.0-alpha.4";
1700
1728
  const packageData = {
1701
1729
  version: version};
1702
1730
 
@@ -14337,7 +14365,7 @@ function createRoot(canvas) {
14337
14365
  const handleXRFrame = (timestamp, _frame) => {
14338
14366
  const state2 = store.getState();
14339
14367
  if (state2.frameloop === "never") return;
14340
- advance(timestamp);
14368
+ advance(timestamp, true, state2);
14341
14369
  };
14342
14370
  const actualRenderer = state.internal.actualRenderer;
14343
14371
  const handleSessionChange = () => {
@@ -14345,7 +14373,7 @@ function createRoot(canvas) {
14345
14373
  const renderer2 = state2.internal.actualRenderer;
14346
14374
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14347
14375
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14348
- if (!renderer2.xr.isPresenting) invalidate();
14376
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14349
14377
  };
14350
14378
  const xr = {
14351
14379
  connect() {
@@ -14427,7 +14455,11 @@ function createRoot(canvas) {
14427
14455
  const newRootId = canvasId || scheduler$1.generateRootId();
14428
14456
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14429
14457
  getState: () => store.getState(),
14430
- 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
14431
14463
  });
14432
14464
  const unregisterCanvasTarget = scheduler$1.register(
14433
14465
  () => {
@@ -14435,6 +14467,10 @@ function createRoot(canvas) {
14435
14467
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14436
14468
  const renderer2 = state2.internal.actualRenderer;
14437
14469
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14470
+ if (state2.internal.canvasTargetSizeDirty) {
14471
+ state2.internal.canvasTargetSizeDirty = false;
14472
+ renderer2.backend?.updateSize?.();
14473
+ }
14438
14474
  }
14439
14475
  },
14440
14476
  {
@@ -14503,8 +14539,7 @@ function createRoot(canvas) {
14503
14539
  phase: "render",
14504
14540
  system: true,
14505
14541
  // Internal flag: this is a system job, not user-controlled
14506
- // Apply scheduler config for render ordering and rate limiting
14507
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14542
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14508
14543
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14509
14544
  }
14510
14545
  );
@@ -14523,8 +14558,21 @@ function createRoot(canvas) {
14523
14558
  scheduler: scheduler$1
14524
14559
  }
14525
14560
  }));
14561
+ } else {
14562
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14563
+ if (constraintsChanged) {
14564
+ scheduler$1.setRootConstraints(rootId, {
14565
+ before: schedulerConfig?.before,
14566
+ after: schedulerConfig?.after
14567
+ });
14568
+ }
14569
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14570
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14571
+ }
14526
14572
  }
14527
- scheduler$1.frameloop = frameloop;
14573
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14574
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14575
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14528
14576
  onCreated = onCreatedCallback;
14529
14577
  configured = true;
14530
14578
  resolve();
@@ -14566,14 +14614,19 @@ function unmountComponentAtNode(canvas, callback) {
14566
14614
  const fiber = root?.fiber;
14567
14615
  if (fiber) {
14568
14616
  const state = root?.store.getState();
14569
- if (state) state.internal.active = false;
14617
+ if (state) {
14618
+ state.internal.active = false;
14619
+ const unregisterRoot = state.internal.unregisterRoot;
14620
+ if (unregisterRoot) {
14621
+ unregisterRoot();
14622
+ state.internal.unregisterRoot = void 0;
14623
+ }
14624
+ }
14570
14625
  reconciler.updateContainer(null, fiber, null, () => {
14571
14626
  if (state) {
14572
14627
  setTimeout(() => {
14573
14628
  try {
14574
14629
  const renderer = state.internal.actualRenderer;
14575
- const unregisterRoot = state.internal.unregisterRoot;
14576
- if (unregisterRoot) unregisterRoot();
14577
14630
  const unregisterPrimary = state.internal.unregisterPrimary;
14578
14631
  if (unregisterPrimary) unregisterPrimary();
14579
14632
  const canvasTarget = state.internal.canvasTarget;
@@ -14739,7 +14792,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14739
14792
  };
14740
14793
  }
14741
14794
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14742
- const defaultScene = useThree((state) => state.scene);
14795
+ const defaultScene = useThree$1((state) => state.scene);
14743
14796
  React__namespace.useLayoutEffect(() => {
14744
14797
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14745
14798
  });
@@ -14757,7 +14810,7 @@ function EnvironmentCube({
14757
14810
  ...rest
14758
14811
  }) {
14759
14812
  const texture = useEnvironment(rest);
14760
- const defaultScene = useThree((state) => state.scene);
14813
+ const defaultScene = useThree$1((state) => state.scene);
14761
14814
  React__namespace.useLayoutEffect(() => {
14762
14815
  return setEnvProps(background, scene, defaultScene, texture, {
14763
14816
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14794,9 +14847,9 @@ function EnvironmentPortal({
14794
14847
  preset = void 0,
14795
14848
  extensions
14796
14849
  }) {
14797
- const renderer = useThree((state) => state.renderer);
14798
- const isLegacy = useThree((state) => state.isLegacy);
14799
- const defaultScene = useThree((state) => state.scene);
14850
+ const renderer = useThree$1((state) => state.renderer);
14851
+ const isLegacy = useThree$1((state) => state.isLegacy);
14852
+ const defaultScene = useThree$1((state) => state.scene);
14800
14853
  const camera = React__namespace.useRef(null);
14801
14854
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
14802
14855
  const fbo = React__namespace.useMemo(() => {
@@ -14840,7 +14893,7 @@ function EnvironmentPortal({
14840
14893
  environmentRotation
14841
14894
  ]);
14842
14895
  let count = 1;
14843
- useFrame(() => {
14896
+ useFrame$1(() => {
14844
14897
  if (frames === Infinity || count < frames) {
14845
14898
  const autoClear = renderer.autoClear;
14846
14899
  renderer.autoClear = true;
@@ -14880,7 +14933,7 @@ function EnvironmentGround(props) {
14880
14933
  ] });
14881
14934
  }
14882
14935
  function EnvironmentColor({ color, scene }) {
14883
- const defaultScene = useThree((state) => state.scene);
14936
+ const defaultScene = useThree$1((state) => state.scene);
14884
14937
  React__namespace.useLayoutEffect(() => {
14885
14938
  if (color === void 0) return;
14886
14939
  const target = resolveScene(scene || defaultScene);
@@ -15118,9 +15171,9 @@ function CanvasImpl({
15118
15171
  React__namespace.useMemo(() => extend(THREE), []);
15119
15172
  const Bridge = useBridge();
15120
15173
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15121
- const hasInitialSizeRef = React__namespace.useRef(false);
15174
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15122
15175
  const measureConfig = React__namespace.useMemo(() => {
15123
- if (!hasInitialSizeRef.current) {
15176
+ if (!hasInitialSize) {
15124
15177
  return {
15125
15178
  ...resize,
15126
15179
  scroll: resize?.scroll ?? true,
@@ -15132,7 +15185,7 @@ function CanvasImpl({
15132
15185
  debounce: { scroll: 50, resize: 0 },
15133
15186
  ...resize
15134
15187
  };
15135
- }, [resize, hasInitialSizeRef.current]);
15188
+ }, [hasInitialSize, resize]);
15136
15189
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15137
15190
  const effectiveSize = React__namespace.useMemo(() => {
15138
15191
  let w = width ?? containerRect.width;
@@ -15148,9 +15201,7 @@ function CanvasImpl({
15148
15201
  left: containerRect.left
15149
15202
  };
15150
15203
  }, [width, height, containerRect, forceEven]);
15151
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15152
- hasInitialSizeRef.current = true;
15153
- }
15204
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15154
15205
  const canvasRef = React__namespace.useRef(null);
15155
15206
  const divRef = React__namespace.useRef(null);
15156
15207
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15264,6 +15315,7 @@ function CanvasImpl({
15264
15315
  const canvas = canvasRef.current;
15265
15316
  if (canvas) {
15266
15317
  return () => {
15318
+ if (canvas.isConnected) return;
15267
15319
  unmountComponentAtNode(canvas);
15268
15320
  root.current = null;
15269
15321
  };
@@ -15474,6 +15526,11 @@ function createTextureOperations(set) {
15474
15526
  removeMultiple: (keys) => removeTextures(set, keys)
15475
15527
  };
15476
15528
  }
15529
+ function scopedNodeName(scope, name) {
15530
+ const raw = scope ? `${scope}_${name}` : name;
15531
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15532
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15533
+ }
15477
15534
  function extractTSLValue(value) {
15478
15535
  if (value === null || value === void 0) return value;
15479
15536
  if (typeof value !== "object") return value;
@@ -15672,8 +15729,7 @@ function createUniform(inName, node, scope) {
15672
15729
  const inValue = vectorize(node);
15673
15730
  const newUniform = tsl.uniform(inValue);
15674
15731
  if (typeof newUniform.setName === "function") {
15675
- const name = scope ? `${scope}_${inName}` : inName;
15676
- newUniform.setName(name);
15732
+ newUniform.setName(scopedNodeName(scope, inName));
15677
15733
  }
15678
15734
  return newUniform;
15679
15735
  }
@@ -15772,7 +15828,7 @@ function useNodes(creatorOrScope, scope) {
15772
15828
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15773
15829
  },
15774
15830
  prepare: (name, node) => {
15775
- node.setName?.(scope ? `${scope}.${name}` : name);
15831
+ node.setName?.(scopedNodeName(scope, name));
15776
15832
  return node;
15777
15833
  }
15778
15834
  });
@@ -15881,7 +15937,7 @@ function useBuffers(creatorOrScope, scope) {
15881
15937
  },
15882
15938
  prepare: (name, buffer) => {
15883
15939
  if ("setName" in buffer && typeof buffer.setName === "function") {
15884
- buffer.setName(scope ? `${scope}.${name}` : name);
15940
+ buffer.setName(scopedNodeName(scope, name));
15885
15941
  }
15886
15942
  return buffer;
15887
15943
  }
@@ -15952,7 +16008,7 @@ function useGPUStorage(creatorOrScope, scope) {
15952
16008
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15953
16009
  },
15954
16010
  prepare: (name, storage) => {
15955
- const label = scope ? `${scope}.${name}` : name;
16011
+ const label = scopedNodeName(scope, name);
15956
16012
  if ("setName" in storage && typeof storage.setName === "function") {
15957
16013
  storage.setName(label);
15958
16014
  }
@@ -15983,9 +16039,10 @@ function rebuildAllStorage(store, scope) {
15983
16039
 
15984
16040
  function useRenderPipeline(mainCB, setupCB) {
15985
16041
  const store = useStore();
15986
- const { scene, camera, renderer, isLegacy } = useThree();
16042
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15987
16043
  const callbacksRanRef = React.useRef(false);
15988
16044
  const scenePassCacheRef = React.useRef(null);
16045
+ const forceScenePassRebuildRef = React.useRef(false);
15989
16046
  const mainCBRef = React.useRef(mainCB);
15990
16047
  const setupCBRef = React.useRef(setupCB);
15991
16048
  mainCBRef.current = mainCB;
@@ -15993,7 +16050,7 @@ function useRenderPipeline(mainCB, setupCB) {
15993
16050
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15994
16051
  React.useEffect(() => {
15995
16052
  callbacksRanRef.current = false;
15996
- scenePassCacheRef.current = null;
16053
+ forceScenePassRebuildRef.current = true;
15997
16054
  }, []);
15998
16055
  const clearPasses = React.useCallback(() => {
15999
16056
  store.setState({ passes: {} });
@@ -16003,12 +16060,14 @@ function useRenderPipeline(mainCB, setupCB) {
16003
16060
  renderPipeline: null,
16004
16061
  passes: {}
16005
16062
  });
16063
+ scenePassCacheRef.current?.scenePass?.dispose?.();
16006
16064
  callbacksRanRef.current = false;
16007
16065
  scenePassCacheRef.current = null;
16066
+ forceScenePassRebuildRef.current = false;
16008
16067
  }, [store]);
16009
16068
  const rebuild = React.useCallback(() => {
16010
16069
  callbacksRanRef.current = false;
16011
- scenePassCacheRef.current = null;
16070
+ forceScenePassRebuildRef.current = true;
16012
16071
  setRebuildVersion((v) => v + 1);
16013
16072
  }, []);
16014
16073
  React.useLayoutEffect(() => {
@@ -16026,21 +16085,31 @@ function useRenderPipeline(mainCB, setupCB) {
16026
16085
  pipeline = new webgpu.RenderPipeline(renderer);
16027
16086
  justCreatedPipeline = true;
16028
16087
  }
16029
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16088
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16089
+ let outgoingScenePass = null;
16030
16090
  let scenePass;
16031
16091
  if (cacheValid) {
16032
16092
  scenePass = scenePassCacheRef.current.scenePass;
16033
16093
  } else {
16094
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16034
16095
  scenePass = tsl.pass(scene, camera);
16035
16096
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16097
+ forceScenePassRebuildRef.current = false;
16036
16098
  }
16037
16099
  currentPasses.scenePass = scenePass;
16038
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16100
+ let outputNodeChanged = false;
16101
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16102
+ pipeline.outputNode = scenePass;
16103
+ outputNodeChanged = true;
16104
+ }
16039
16105
  set({ renderPipeline: pipeline, passes: currentPasses });
16040
16106
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16041
16107
  if (shouldRunCallbacks) {
16042
16108
  if (setupCBRef.current) {
16043
- const freshState = store.getState();
16109
+ const freshState = Object.assign({}, store.getState(), {
16110
+ renderPipeline: pipeline,
16111
+ passes: currentPasses
16112
+ });
16044
16113
  const setupResult = setupCBRef.current(freshState);
16045
16114
  if (setupResult && typeof setupResult === "object") {
16046
16115
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16048,21 +16117,27 @@ function useRenderPipeline(mainCB, setupCB) {
16048
16117
  }
16049
16118
  }
16050
16119
  if (mainCBRef.current) {
16051
- const freshState = store.getState();
16120
+ const freshState = Object.assign({}, store.getState(), {
16121
+ renderPipeline: pipeline,
16122
+ passes: currentPasses
16123
+ });
16052
16124
  const mainResult = mainCBRef.current(freshState);
16053
16125
  if (mainResult && typeof mainResult === "object") {
16054
16126
  currentPasses = { ...currentPasses, ...mainResult };
16055
16127
  set({ passes: currentPasses });
16056
16128
  }
16057
16129
  }
16130
+ outputNodeChanged = true;
16058
16131
  callbacksRanRef.current = true;
16059
16132
  }
16133
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16134
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose?.();
16060
16135
  } catch (error) {
16061
16136
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16062
16137
  }
16063
16138
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16064
- const passes = useThree((s) => s.passes);
16065
- const renderPipeline = useThree((s) => s.renderPipeline);
16139
+ const passes = useThree$1((s) => s.passes);
16140
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16066
16141
  return {
16067
16142
  passes,
16068
16143
  renderPipeline,
@@ -16075,6 +16150,8 @@ function useRenderPipeline(mainCB, setupCB) {
16075
16150
  }
16076
16151
 
16077
16152
  extend(THREE);
16153
+ const useThree = useThree$1;
16154
+ const useFrame = useFrame$1;
16078
16155
 
16079
16156
  exports.Scheduler = scheduler.Scheduler;
16080
16157
  exports.getScheduler = scheduler.getScheduler;