@react-three/fiber 10.0.0-canary.0d86c23 → 10.0.0-canary.0ea97c0

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();
@@ -629,7 +632,12 @@ function diffProps(instance, newProps) {
629
632
  const changedProps = {};
630
633
  for (const prop in newProps) {
631
634
  if (RESERVED_PROPS.includes(prop)) continue;
632
- if (is.equ(newProps[prop], instance.props[prop])) continue;
635
+ if (is.equ(newProps[prop], instance.props[prop])) {
636
+ const value = newProps[prop];
637
+ if (!isCopyable(value)) continue;
638
+ const { target } = resolve(instance.object, prop);
639
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
640
+ }
633
641
  changedProps[prop] = newProps[prop];
634
642
  for (const other in newProps) {
635
643
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -932,7 +940,7 @@ const createStore = (invalidate, advance) => {
932
940
  size: newSize,
933
941
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
934
942
  }));
935
- scheduler.getScheduler().invalidate();
943
+ get().invalidate();
936
944
  }
937
945
  }
938
946
  return;
@@ -947,15 +955,16 @@ const createStore = (invalidate, advance) => {
947
955
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
948
956
  _sizeImperative: true
949
957
  }));
950
- scheduler.getScheduler().invalidate();
958
+ get().invalidate();
951
959
  },
952
960
  setDpr: (dpr) => set((state2) => {
953
961
  const resolved = calculateDpr(dpr);
954
962
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
955
963
  }),
956
964
  setFrameloop: (frameloop = "always") => {
965
+ const rootId = get().internal.rootId;
966
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
957
967
  set(() => ({ frameloop }));
958
- scheduler.getScheduler().frameloop = frameloop;
959
968
  },
960
969
  setError: (error) => set(() => ({ error })),
961
970
  error: null,
@@ -1071,17 +1080,11 @@ const createStore = (invalidate, advance) => {
1071
1080
  oldSize = size;
1072
1081
  oldDpr = viewport.dpr;
1073
1082
  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
- }
1083
+ const resizeTarget = canvasTarget ?? actualRenderer;
1084
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1085
+ resizeTarget.setSize(size.width, size.height, false);
1086
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1087
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1085
1088
  }
1086
1089
  if (camera !== oldCamera) {
1087
1090
  oldCamera = camera;
@@ -1143,7 +1146,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1143
1146
  const results = cacheKeys.map(
1144
1147
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1145
1148
  );
1146
- return Array.isArray(input) ? results : results[0];
1149
+ const stableRef = React.useRef(null);
1150
+ const previous = stableRef.current;
1151
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1152
+ stableRef.current = stable;
1153
+ return Array.isArray(input) ? stable : stable[0];
1147
1154
  }
1148
1155
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1149
1156
  const inputs = Array.isArray(input) ? input : [input];
@@ -1158,7 +1165,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1158
1165
  };
1159
1166
  useLoader.loader = getLoader$1;
1160
1167
 
1161
- function useFrame(callback, priorityOrOptions) {
1168
+ function useFrame$1(callback, priorityOrOptions) {
1162
1169
  const store = React__namespace.useContext(context);
1163
1170
  const isInsideCanvas = store !== null;
1164
1171
  const scheduler$1 = scheduler.getScheduler();
@@ -1263,6 +1270,13 @@ function useFrame(callback, priorityOrOptions) {
1263
1270
  * Use for controlling the entire frame loop, adding phases, etc.
1264
1271
  */
1265
1272
  scheduler: scheduler2,
1273
+ /**
1274
+ * The root that currently owns this job. Resolve on access because ambient
1275
+ * jobs can be adopted by a Canvas after the controls object is created.
1276
+ */
1277
+ get rootId() {
1278
+ return scheduler.getScheduler().getJobRootId(id);
1279
+ },
1266
1280
  /**
1267
1281
  * Manually step this job only.
1268
1282
  * Bypasses FPS limiting - always runs.
@@ -1279,6 +1293,14 @@ function useFrame(callback, priorityOrOptions) {
1279
1293
  stepAll: (timestamp) => {
1280
1294
  scheduler.getScheduler().step(timestamp);
1281
1295
  },
1296
+ /**
1297
+ * Request frames for this job's owning root without waking sibling roots.
1298
+ */
1299
+ invalidate: (frames, stackFrames) => {
1300
+ const scheduler3 = scheduler.getScheduler();
1301
+ const rootId = scheduler3.getJobRootId(id);
1302
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1303
+ },
1282
1304
  /**
1283
1305
  * Pause this job (set enabled=false).
1284
1306
  * Job remains registered but won't run.
@@ -1308,6 +1330,11 @@ function getUrls(input) {
1308
1330
  if (Array.isArray(input)) return input;
1309
1331
  return Object.values(input);
1310
1332
  }
1333
+ function inputSignature(input) {
1334
+ if (typeof input === "string") return input;
1335
+ if (Array.isArray(input)) return input.join("\0");
1336
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1337
+ }
1311
1338
  function allUrlsCached(urls, textureCache) {
1312
1339
  return urls.every((url) => textureCache.has(url));
1313
1340
  }
@@ -1326,25 +1353,26 @@ function buildFromCache(input, textureCache) {
1326
1353
  }
1327
1354
  }
1328
1355
  function useTexture(input, optionsOrOnLoad) {
1329
- const renderer = useThree((state) => state.internal.actualRenderer);
1356
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1330
1357
  const store = useStore();
1331
1358
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1332
1359
  const { onLoad, cache = true } = options;
1333
1360
  const onLoadRef = React.useRef(onLoad);
1334
1361
  onLoadRef.current = onLoad;
1335
1362
  const onLoadCalledForRef = React.useRef(null);
1336
- const urls = React.useMemo(() => getUrls(input), [input]);
1363
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1364
+ const stableInput = React.useMemo(() => input, [inputKey]);
1365
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1337
1366
  const cachedResult = React.useMemo(() => {
1338
1367
  if (!cache) return null;
1339
1368
  const textures = store.getState().textures;
1340
1369
  if (!allUrlsCached(urls, textures)) return null;
1341
- return buildFromCache(input, textures);
1342
- }, [cache, urls, input, store]);
1370
+ return buildFromCache(stableInput, textures);
1371
+ }, [cache, urls, stableInput, store]);
1343
1372
  const loadedTextures = useLoader(
1344
1373
  webgpu.TextureLoader,
1345
- IsObject(input) ? Object.values(input) : input
1374
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1346
1375
  );
1347
- const inputKey = urls.join("\0");
1348
1376
  React.useLayoutEffect(() => {
1349
1377
  if (cachedResult) return;
1350
1378
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1371,28 +1399,28 @@ function useTexture(input, optionsOrOnLoad) {
1371
1399
  }, [renderer, loadedTextures, cachedResult]);
1372
1400
  const mappedTextures = React.useMemo(() => {
1373
1401
  if (cachedResult) return cachedResult;
1374
- if (IsObject(input)) {
1402
+ if (IsObject(stableInput)) {
1375
1403
  const keyed = {};
1376
1404
  const textureArray = loadedTextures;
1377
1405
  let i = 0;
1378
- for (const key in input) keyed[key] = textureArray[i++];
1406
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1379
1407
  return keyed;
1380
1408
  } else {
1381
1409
  return loadedTextures;
1382
1410
  }
1383
- }, [input, loadedTextures, cachedResult]);
1411
+ }, [stableInput, loadedTextures, cachedResult]);
1384
1412
  React.useEffect(() => {
1385
1413
  if (!cache) return;
1386
1414
  const urlTextureMap = [];
1387
- if (typeof input === "string") {
1388
- urlTextureMap.push([input, mappedTextures]);
1389
- } else if (Array.isArray(input)) {
1415
+ if (typeof stableInput === "string") {
1416
+ urlTextureMap.push([stableInput, mappedTextures]);
1417
+ } else if (Array.isArray(stableInput)) {
1390
1418
  const textureArray = mappedTextures;
1391
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1392
- } else if (IsObject(input)) {
1419
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1420
+ } else if (IsObject(stableInput)) {
1393
1421
  const textureRecord = mappedTextures;
1394
- for (const key in input) {
1395
- const url = input[key];
1422
+ for (const key in stableInput) {
1423
+ const url = stableInput[key];
1396
1424
  urlTextureMap.push([url, textureRecord[key]]);
1397
1425
  }
1398
1426
  }
@@ -1421,7 +1449,7 @@ function useTexture(input, optionsOrOnLoad) {
1421
1449
  }
1422
1450
  return { _textureRefs: refs };
1423
1451
  });
1424
- }, [cache, input, mappedTextures, store]);
1452
+ }, [cache, stableInput, mappedTextures, store]);
1425
1453
  return mappedTextures;
1426
1454
  }
1427
1455
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1492,13 +1520,13 @@ function useTextures(selector) {
1492
1520
  };
1493
1521
  }, [store]);
1494
1522
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1495
- const selected = useThree(subscribe);
1523
+ const selected = useThree$1(subscribe);
1496
1524
  return selector ? selected : registry;
1497
1525
  }
1498
1526
 
1499
1527
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1500
- const isLegacy = useThree((s) => s.isLegacy);
1501
- const size = useThree((s) => s.size);
1528
+ const isLegacy = useThree$1((s) => s.isLegacy);
1529
+ const size = useThree$1((s) => s.size);
1502
1530
  let width;
1503
1531
  let height;
1504
1532
  let opts;
@@ -1555,7 +1583,7 @@ function useEnvironment({
1555
1583
  const { extension, isCubemap } = getExtension(files);
1556
1584
  const loader = getLoader(extension);
1557
1585
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1558
- const renderer = useThree((state) => state.renderer);
1586
+ const renderer = useThree$1((state) => state.renderer);
1559
1587
  React.useLayoutEffect(() => {
1560
1588
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1561
1589
  function clearGainmapTexture() {
@@ -1648,7 +1676,7 @@ function useStore() {
1648
1676
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1649
1677
  return store;
1650
1678
  }
1651
- function useThree(selector = (state) => state, equalityFn) {
1679
+ function useThree$1(selector = (state) => state, equalityFn) {
1652
1680
  return useStore()(selector, equalityFn);
1653
1681
  }
1654
1682
  function useInstanceHandle(ref) {
@@ -1690,13 +1718,19 @@ function addTail(callback) {
1690
1718
  return scheduler.getScheduler().onIdle(callback);
1691
1719
  }
1692
1720
  function invalidate(state, frames = 1, stackFrames = false) {
1693
- scheduler.getScheduler().invalidate(frames, stackFrames);
1721
+ const rootId = state?.internal?.rootId;
1722
+ const scheduler$1 = scheduler.getScheduler();
1723
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1724
+ else scheduler$1.invalidate(frames, stackFrames);
1694
1725
  }
1695
- function advance(timestamp) {
1696
- scheduler.getScheduler().step(timestamp);
1726
+ function advance(timestamp, runGlobalEffects, state) {
1727
+ const rootId = state?.internal?.rootId;
1728
+ const scheduler$1 = scheduler.getScheduler();
1729
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1730
+ else scheduler$1.step(timestamp);
1697
1731
  }
1698
1732
 
1699
- const version = "10.0.0-alpha.3";
1733
+ const version = "10.0.0-alpha.4";
1700
1734
  const packageData = {
1701
1735
  version: version};
1702
1736
 
@@ -14212,9 +14246,8 @@ function createRoot(canvas) {
14212
14246
  if (!renderer.hasInitialized?.()) {
14213
14247
  const size2 = computeInitialSize(canvas, propsSize);
14214
14248
  if (size2.width > 0 && size2.height > 0) {
14215
- const pixelRatio = calculateDpr(dpr);
14216
- canvas.width = size2.width * pixelRatio;
14217
- canvas.height = size2.height * pixelRatio;
14249
+ renderer.setPixelRatio(calculateDpr(dpr));
14250
+ renderer.setSize(size2.width, size2.height, false);
14218
14251
  }
14219
14252
  await renderer.init();
14220
14253
  }
@@ -14223,7 +14256,7 @@ function createRoot(canvas) {
14223
14256
  state.internal.actualRenderer = renderer;
14224
14257
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14225
14258
  if (canvasId && !state.internal.isSecondary) {
14226
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14259
+ const canvasTarget = renderer.getCanvasTarget?.();
14227
14260
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14228
14261
  state.set((prev) => ({
14229
14262
  internal: {
@@ -14337,7 +14370,7 @@ function createRoot(canvas) {
14337
14370
  const handleXRFrame = (timestamp, _frame) => {
14338
14371
  const state2 = store.getState();
14339
14372
  if (state2.frameloop === "never") return;
14340
- advance(timestamp);
14373
+ advance(timestamp, true, state2);
14341
14374
  };
14342
14375
  const actualRenderer = state.internal.actualRenderer;
14343
14376
  const handleSessionChange = () => {
@@ -14345,7 +14378,7 @@ function createRoot(canvas) {
14345
14378
  const renderer2 = state2.internal.actualRenderer;
14346
14379
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14347
14380
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14348
- if (!renderer2.xr.isPresenting) invalidate();
14381
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14349
14382
  };
14350
14383
  const xr = {
14351
14384
  connect() {
@@ -14427,14 +14460,23 @@ function createRoot(canvas) {
14427
14460
  const newRootId = canvasId || scheduler$1.generateRootId();
14428
14461
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14429
14462
  getState: () => store.getState(),
14430
- onError: (err) => store.getState().setError(err)
14463
+ onError: (err) => store.getState().setError(err),
14464
+ frameloop: store.getState().frameloop,
14465
+ before: schedulerConfig?.before,
14466
+ after: schedulerConfig?.after,
14467
+ order: schedulerConfig?.order
14431
14468
  });
14432
14469
  const unregisterCanvasTarget = scheduler$1.register(
14433
14470
  () => {
14434
14471
  const state2 = store.getState();
14435
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14472
+ const canvasTarget = state2.internal.canvasTarget;
14473
+ if (canvasTarget) {
14436
14474
  const renderer2 = state2.internal.actualRenderer;
14437
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14475
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14476
+ if (state2.internal.canvasTargetSizeDirty) {
14477
+ state2.internal.canvasTargetSizeDirty = false;
14478
+ renderer2.backend?.updateSize?.();
14479
+ }
14438
14480
  }
14439
14481
  },
14440
14482
  {
@@ -14503,8 +14545,7 @@ function createRoot(canvas) {
14503
14545
  phase: "render",
14504
14546
  system: true,
14505
14547
  // 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 },
14548
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14508
14549
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14509
14550
  }
14510
14551
  );
@@ -14523,8 +14564,21 @@ function createRoot(canvas) {
14523
14564
  scheduler: scheduler$1
14524
14565
  }
14525
14566
  }));
14567
+ } else {
14568
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14569
+ if (constraintsChanged) {
14570
+ scheduler$1.setRootConstraints(rootId, {
14571
+ before: schedulerConfig?.before,
14572
+ after: schedulerConfig?.after
14573
+ });
14574
+ }
14575
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14576
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14577
+ }
14526
14578
  }
14527
- scheduler$1.frameloop = frameloop;
14579
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14580
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14581
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14528
14582
  onCreated = onCreatedCallback;
14529
14583
  configured = true;
14530
14584
  resolve();
@@ -14566,18 +14620,23 @@ function unmountComponentAtNode(canvas, callback) {
14566
14620
  const fiber = root?.fiber;
14567
14621
  if (fiber) {
14568
14622
  const state = root?.store.getState();
14569
- if (state) state.internal.active = false;
14623
+ if (state) {
14624
+ state.internal.active = false;
14625
+ const unregisterRoot = state.internal.unregisterRoot;
14626
+ if (unregisterRoot) {
14627
+ unregisterRoot();
14628
+ state.internal.unregisterRoot = void 0;
14629
+ }
14630
+ }
14570
14631
  reconciler.updateContainer(null, fiber, null, () => {
14571
14632
  if (state) {
14572
14633
  setTimeout(() => {
14573
14634
  try {
14574
14635
  const renderer = state.internal.actualRenderer;
14575
- const unregisterRoot = state.internal.unregisterRoot;
14576
- if (unregisterRoot) unregisterRoot();
14577
14636
  const unregisterPrimary = state.internal.unregisterPrimary;
14578
14637
  if (unregisterPrimary) unregisterPrimary();
14579
14638
  const canvasTarget = state.internal.canvasTarget;
14580
- if (canvasTarget?.dispose) canvasTarget.dispose();
14639
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14581
14640
  state.events.disconnect?.();
14582
14641
  cleanupHelperGroup(root.store);
14583
14642
  if (state.isLegacy && renderer) {
@@ -14739,7 +14798,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14739
14798
  };
14740
14799
  }
14741
14800
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14742
- const defaultScene = useThree((state) => state.scene);
14801
+ const defaultScene = useThree$1((state) => state.scene);
14743
14802
  React__namespace.useLayoutEffect(() => {
14744
14803
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14745
14804
  });
@@ -14757,7 +14816,7 @@ function EnvironmentCube({
14757
14816
  ...rest
14758
14817
  }) {
14759
14818
  const texture = useEnvironment(rest);
14760
- const defaultScene = useThree((state) => state.scene);
14819
+ const defaultScene = useThree$1((state) => state.scene);
14761
14820
  React__namespace.useLayoutEffect(() => {
14762
14821
  return setEnvProps(background, scene, defaultScene, texture, {
14763
14822
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14794,9 +14853,9 @@ function EnvironmentPortal({
14794
14853
  preset = void 0,
14795
14854
  extensions
14796
14855
  }) {
14797
- const renderer = useThree((state) => state.renderer);
14798
- const isLegacy = useThree((state) => state.isLegacy);
14799
- const defaultScene = useThree((state) => state.scene);
14856
+ const renderer = useThree$1((state) => state.renderer);
14857
+ const isLegacy = useThree$1((state) => state.isLegacy);
14858
+ const defaultScene = useThree$1((state) => state.scene);
14800
14859
  const camera = React__namespace.useRef(null);
14801
14860
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
14802
14861
  const fbo = React__namespace.useMemo(() => {
@@ -14840,7 +14899,7 @@ function EnvironmentPortal({
14840
14899
  environmentRotation
14841
14900
  ]);
14842
14901
  let count = 1;
14843
- useFrame(() => {
14902
+ useFrame$1(() => {
14844
14903
  if (frames === Infinity || count < frames) {
14845
14904
  const autoClear = renderer.autoClear;
14846
14905
  renderer.autoClear = true;
@@ -14880,7 +14939,7 @@ function EnvironmentGround(props) {
14880
14939
  ] });
14881
14940
  }
14882
14941
  function EnvironmentColor({ color, scene }) {
14883
- const defaultScene = useThree((state) => state.scene);
14942
+ const defaultScene = useThree$1((state) => state.scene);
14884
14943
  React__namespace.useLayoutEffect(() => {
14885
14944
  if (color === void 0) return;
14886
14945
  const target = resolveScene(scene || defaultScene);
@@ -15118,9 +15177,9 @@ function CanvasImpl({
15118
15177
  React__namespace.useMemo(() => extend(THREE), []);
15119
15178
  const Bridge = useBridge();
15120
15179
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15121
- const hasInitialSizeRef = React__namespace.useRef(false);
15180
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15122
15181
  const measureConfig = React__namespace.useMemo(() => {
15123
- if (!hasInitialSizeRef.current) {
15182
+ if (!hasInitialSize) {
15124
15183
  return {
15125
15184
  ...resize,
15126
15185
  scroll: resize?.scroll ?? true,
@@ -15132,7 +15191,7 @@ function CanvasImpl({
15132
15191
  debounce: { scroll: 50, resize: 0 },
15133
15192
  ...resize
15134
15193
  };
15135
- }, [resize, hasInitialSizeRef.current]);
15194
+ }, [hasInitialSize, resize]);
15136
15195
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15137
15196
  const effectiveSize = React__namespace.useMemo(() => {
15138
15197
  let w = width ?? containerRect.width;
@@ -15148,9 +15207,7 @@ function CanvasImpl({
15148
15207
  left: containerRect.left
15149
15208
  };
15150
15209
  }, [width, height, containerRect, forceEven]);
15151
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15152
- hasInitialSizeRef.current = true;
15153
- }
15210
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15154
15211
  const canvasRef = React__namespace.useRef(null);
15155
15212
  const divRef = React__namespace.useRef(null);
15156
15213
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15264,6 +15321,7 @@ function CanvasImpl({
15264
15321
  const canvas = canvasRef.current;
15265
15322
  if (canvas) {
15266
15323
  return () => {
15324
+ if (canvas.isConnected) return;
15267
15325
  unmountComponentAtNode(canvas);
15268
15326
  root.current = null;
15269
15327
  };
@@ -15474,6 +15532,11 @@ function createTextureOperations(set) {
15474
15532
  removeMultiple: (keys) => removeTextures(set, keys)
15475
15533
  };
15476
15534
  }
15535
+ function scopedNodeName(scope, name) {
15536
+ const raw = scope ? `${scope}_${name}` : name;
15537
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15538
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15539
+ }
15477
15540
  function extractTSLValue(value) {
15478
15541
  if (value === null || value === void 0) return value;
15479
15542
  if (typeof value !== "object") return value;
@@ -15672,8 +15735,7 @@ function createUniform(inName, node, scope) {
15672
15735
  const inValue = vectorize(node);
15673
15736
  const newUniform = tsl.uniform(inValue);
15674
15737
  if (typeof newUniform.setName === "function") {
15675
- const name = scope ? `${scope}_${inName}` : inName;
15676
- newUniform.setName(name);
15738
+ newUniform.setName(scopedNodeName(scope, inName));
15677
15739
  }
15678
15740
  return newUniform;
15679
15741
  }
@@ -15772,7 +15834,7 @@ function useNodes(creatorOrScope, scope) {
15772
15834
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15773
15835
  },
15774
15836
  prepare: (name, node) => {
15775
- node.setName?.(scope ? `${scope}.${name}` : name);
15837
+ node.setName?.(scopedNodeName(scope, name));
15776
15838
  return node;
15777
15839
  }
15778
15840
  });
@@ -15881,7 +15943,7 @@ function useBuffers(creatorOrScope, scope) {
15881
15943
  },
15882
15944
  prepare: (name, buffer) => {
15883
15945
  if ("setName" in buffer && typeof buffer.setName === "function") {
15884
- buffer.setName(scope ? `${scope}.${name}` : name);
15946
+ buffer.setName(scopedNodeName(scope, name));
15885
15947
  }
15886
15948
  return buffer;
15887
15949
  }
@@ -15952,7 +16014,7 @@ function useGPUStorage(creatorOrScope, scope) {
15952
16014
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15953
16015
  },
15954
16016
  prepare: (name, storage) => {
15955
- const label = scope ? `${scope}.${name}` : name;
16017
+ const label = scopedNodeName(scope, name);
15956
16018
  if ("setName" in storage && typeof storage.setName === "function") {
15957
16019
  storage.setName(label);
15958
16020
  }
@@ -15983,9 +16045,10 @@ function rebuildAllStorage(store, scope) {
15983
16045
 
15984
16046
  function useRenderPipeline(mainCB, setupCB) {
15985
16047
  const store = useStore();
15986
- const { scene, camera, renderer, isLegacy } = useThree();
16048
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15987
16049
  const callbacksRanRef = React.useRef(false);
15988
16050
  const scenePassCacheRef = React.useRef(null);
16051
+ const forceScenePassRebuildRef = React.useRef(false);
15989
16052
  const mainCBRef = React.useRef(mainCB);
15990
16053
  const setupCBRef = React.useRef(setupCB);
15991
16054
  mainCBRef.current = mainCB;
@@ -15993,7 +16056,7 @@ function useRenderPipeline(mainCB, setupCB) {
15993
16056
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15994
16057
  React.useEffect(() => {
15995
16058
  callbacksRanRef.current = false;
15996
- scenePassCacheRef.current = null;
16059
+ forceScenePassRebuildRef.current = true;
15997
16060
  }, []);
15998
16061
  const clearPasses = React.useCallback(() => {
15999
16062
  store.setState({ passes: {} });
@@ -16003,12 +16066,14 @@ function useRenderPipeline(mainCB, setupCB) {
16003
16066
  renderPipeline: null,
16004
16067
  passes: {}
16005
16068
  });
16069
+ scenePassCacheRef.current?.scenePass.dispose();
16006
16070
  callbacksRanRef.current = false;
16007
16071
  scenePassCacheRef.current = null;
16072
+ forceScenePassRebuildRef.current = false;
16008
16073
  }, [store]);
16009
16074
  const rebuild = React.useCallback(() => {
16010
16075
  callbacksRanRef.current = false;
16011
- scenePassCacheRef.current = null;
16076
+ forceScenePassRebuildRef.current = true;
16012
16077
  setRebuildVersion((v) => v + 1);
16013
16078
  }, []);
16014
16079
  React.useLayoutEffect(() => {
@@ -16016,6 +16081,9 @@ function useRenderPipeline(mainCB, setupCB) {
16016
16081
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16017
16082
  }
16018
16083
  if (!renderer || !scene || !camera) return;
16084
+ if (!("isWebGPURenderer" in renderer)) {
16085
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16086
+ }
16019
16087
  const state = store.getState();
16020
16088
  const set = store.setState;
16021
16089
  try {
@@ -16026,21 +16094,33 @@ function useRenderPipeline(mainCB, setupCB) {
16026
16094
  pipeline = new webgpu.RenderPipeline(renderer);
16027
16095
  justCreatedPipeline = true;
16028
16096
  }
16029
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16097
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16098
+ let outgoingScenePass = null;
16030
16099
  let scenePass;
16031
16100
  if (cacheValid) {
16032
16101
  scenePass = scenePassCacheRef.current.scenePass;
16033
16102
  } else {
16103
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16034
16104
  scenePass = tsl.pass(scene, camera);
16035
16105
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16106
+ forceScenePassRebuildRef.current = false;
16036
16107
  }
16037
16108
  currentPasses.scenePass = scenePass;
16038
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16109
+ let outputNodeChanged = false;
16110
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16111
+ pipeline.outputNode = scenePass;
16112
+ outputNodeChanged = true;
16113
+ }
16039
16114
  set({ renderPipeline: pipeline, passes: currentPasses });
16040
16115
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16041
16116
  if (shouldRunCallbacks) {
16042
16117
  if (setupCBRef.current) {
16043
- const freshState = store.getState();
16118
+ const freshState = Object.assign({}, store.getState(), {
16119
+ renderPipeline: pipeline,
16120
+ passes: { ...currentPasses, scenePass },
16121
+ renderer,
16122
+ isLegacy: false
16123
+ });
16044
16124
  const setupResult = setupCBRef.current(freshState);
16045
16125
  if (setupResult && typeof setupResult === "object") {
16046
16126
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16048,33 +16128,37 @@ function useRenderPipeline(mainCB, setupCB) {
16048
16128
  }
16049
16129
  }
16050
16130
  if (mainCBRef.current) {
16051
- const freshState = store.getState();
16131
+ const freshState = Object.assign({}, store.getState(), {
16132
+ renderPipeline: pipeline,
16133
+ passes: { ...currentPasses, scenePass },
16134
+ renderer,
16135
+ isLegacy: false
16136
+ });
16052
16137
  const mainResult = mainCBRef.current(freshState);
16053
16138
  if (mainResult && typeof mainResult === "object") {
16054
16139
  currentPasses = { ...currentPasses, ...mainResult };
16055
16140
  set({ passes: currentPasses });
16056
16141
  }
16057
16142
  }
16143
+ outputNodeChanged = true;
16058
16144
  callbacksRanRef.current = true;
16059
16145
  }
16146
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16147
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16060
16148
  } catch (error) {
16061
16149
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16062
16150
  }
16063
16151
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16064
- const passes = useThree((s) => s.passes);
16065
- const renderPipeline = useThree((s) => s.renderPipeline);
16066
- return {
16067
- passes,
16068
- renderPipeline,
16069
- clearPasses,
16070
- reset,
16071
- rebuild,
16072
- // isReady indicates if RenderPipeline is configured and ready for rendering
16073
- isReady: renderPipeline !== null
16074
- };
16152
+ const passes = useThree$1((s) => s.passes);
16153
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16154
+ const actions = { passes, clearPasses, reset, rebuild };
16155
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16156
+ return { ...actions, isReady: false, renderPipeline: null };
16075
16157
  }
16076
16158
 
16077
16159
  extend(THREE);
16160
+ const useThree = useThree$1;
16161
+ const useFrame = useFrame$1;
16078
16162
 
16079
16163
  exports.Scheduler = scheduler.Scheduler;
16080
16164
  exports.getScheduler = scheduler.getScheduler;