@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,6 +1,5 @@
1
1
  import * as webgpu from 'three/webgpu';
2
2
  import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color, Vector4, RenderPipeline } from 'three/webgpu';
3
- import { Inspector } from 'three/addons/inspector/Inspector.js';
4
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
4
  import * as React from 'react';
6
5
  import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -34,6 +33,10 @@ function _mergeNamespaces(n, m) {
34
33
 
35
34
  const R3F_BUILD_LEGACY = false;
36
35
  const R3F_BUILD_WEBGPU = true;
36
+ async function loadInspector() {
37
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
38
+ return Inspector;
39
+ }
37
40
  const WebGLRenderer = class WebGLRenderer2 {
38
41
  constructor() {
39
42
  throw new Error(
@@ -47,13 +50,13 @@ const WebGLCubeRenderTarget = null;
47
50
  const THREE = /*#__PURE__*/_mergeNamespaces({
48
51
  __proto__: null,
49
52
  CubeRenderTargetCompat: CubeRenderTarget,
50
- Inspector: Inspector,
51
53
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
52
54
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
53
55
  RenderTargetCompat: RenderTarget,
54
56
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
55
57
  WebGLRenderTarget: WebGLRenderTarget,
56
- WebGLRenderer: WebGLRenderer
58
+ WebGLRenderer: WebGLRenderer,
59
+ loadInspector: loadInspector
57
60
  }, [webgpu]);
58
61
 
59
62
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -912,7 +915,7 @@ const createStore = (invalidate, advance) => {
912
915
  size: newSize,
913
916
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
914
917
  }));
915
- getScheduler().invalidate();
918
+ get().invalidate();
916
919
  }
917
920
  }
918
921
  return;
@@ -927,15 +930,16 @@ const createStore = (invalidate, advance) => {
927
930
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
928
931
  _sizeImperative: true
929
932
  }));
930
- getScheduler().invalidate();
933
+ get().invalidate();
931
934
  },
932
935
  setDpr: (dpr) => set((state2) => {
933
936
  const resolved = calculateDpr(dpr);
934
937
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
935
938
  }),
936
939
  setFrameloop: (frameloop = "always") => {
940
+ const rootId = get().internal.rootId;
941
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
937
942
  set(() => ({ frameloop }));
938
- getScheduler().frameloop = frameloop;
939
943
  },
940
944
  setError: (error) => set(() => ({ error })),
941
945
  error: null,
@@ -1051,17 +1055,10 @@ const createStore = (invalidate, advance) => {
1051
1055
  oldSize = size;
1052
1056
  oldDpr = viewport.dpr;
1053
1057
  updateCamera(camera, size);
1054
- if (internal.isSecondary && canvasTarget) {
1055
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1056
- canvasTarget.setSize(size.width, size.height, false);
1057
- } else {
1058
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1059
- actualRenderer.setSize(size.width, size.height, false);
1060
- if (canvasTarget) {
1061
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1062
- canvasTarget.setSize(size.width, size.height, false);
1063
- }
1064
- }
1058
+ const resizeTarget = canvasTarget ?? actualRenderer;
1059
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1060
+ resizeTarget.setSize(size.width, size.height, false);
1061
+ internal.canvasTargetSizeDirty = true;
1065
1062
  }
1066
1063
  if (camera !== oldCamera) {
1067
1064
  oldCamera = camera;
@@ -1123,7 +1120,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1123
1120
  const results = cacheKeys.map(
1124
1121
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1125
1122
  );
1126
- return Array.isArray(input) ? results : results[0];
1123
+ const stableRef = useRef(null);
1124
+ const previous = stableRef.current;
1125
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1126
+ stableRef.current = stable;
1127
+ return Array.isArray(input) ? stable : stable[0];
1127
1128
  }
1128
1129
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1129
1130
  const inputs = Array.isArray(input) ? input : [input];
@@ -1138,7 +1139,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1138
1139
  };
1139
1140
  useLoader.loader = getLoader$1;
1140
1141
 
1141
- function useFrame(callback, priorityOrOptions) {
1142
+ function useFrame$1(callback, priorityOrOptions) {
1142
1143
  const store = React.useContext(context);
1143
1144
  const isInsideCanvas = store !== null;
1144
1145
  const scheduler = getScheduler();
@@ -1243,6 +1244,13 @@ function useFrame(callback, priorityOrOptions) {
1243
1244
  * Use for controlling the entire frame loop, adding phases, etc.
1244
1245
  */
1245
1246
  scheduler: scheduler2,
1247
+ /**
1248
+ * The root that currently owns this job. Resolve on access because ambient
1249
+ * jobs can be adopted by a Canvas after the controls object is created.
1250
+ */
1251
+ get rootId() {
1252
+ return getScheduler().getJobRootId(id);
1253
+ },
1246
1254
  /**
1247
1255
  * Manually step this job only.
1248
1256
  * Bypasses FPS limiting - always runs.
@@ -1259,6 +1267,14 @@ function useFrame(callback, priorityOrOptions) {
1259
1267
  stepAll: (timestamp) => {
1260
1268
  getScheduler().step(timestamp);
1261
1269
  },
1270
+ /**
1271
+ * Request frames for this job's owning root without waking sibling roots.
1272
+ */
1273
+ invalidate: (frames, stackFrames) => {
1274
+ const scheduler3 = getScheduler();
1275
+ const rootId = scheduler3.getJobRootId(id);
1276
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1277
+ },
1262
1278
  /**
1263
1279
  * Pause this job (set enabled=false).
1264
1280
  * Job remains registered but won't run.
@@ -1288,6 +1304,11 @@ function getUrls(input) {
1288
1304
  if (Array.isArray(input)) return input;
1289
1305
  return Object.values(input);
1290
1306
  }
1307
+ function inputSignature(input) {
1308
+ if (typeof input === "string") return input;
1309
+ if (Array.isArray(input)) return input.join("\0");
1310
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1311
+ }
1291
1312
  function allUrlsCached(urls, textureCache) {
1292
1313
  return urls.every((url) => textureCache.has(url));
1293
1314
  }
@@ -1306,25 +1327,26 @@ function buildFromCache(input, textureCache) {
1306
1327
  }
1307
1328
  }
1308
1329
  function useTexture(input, optionsOrOnLoad) {
1309
- const renderer = useThree((state) => state.internal.actualRenderer);
1330
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1310
1331
  const store = useStore();
1311
1332
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1312
1333
  const { onLoad, cache = true } = options;
1313
1334
  const onLoadRef = useRef(onLoad);
1314
1335
  onLoadRef.current = onLoad;
1315
1336
  const onLoadCalledForRef = useRef(null);
1316
- const urls = useMemo(() => getUrls(input), [input]);
1337
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1338
+ const stableInput = useMemo(() => input, [inputKey]);
1339
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1317
1340
  const cachedResult = useMemo(() => {
1318
1341
  if (!cache) return null;
1319
1342
  const textures = store.getState().textures;
1320
1343
  if (!allUrlsCached(urls, textures)) return null;
1321
- return buildFromCache(input, textures);
1322
- }, [cache, urls, input, store]);
1344
+ return buildFromCache(stableInput, textures);
1345
+ }, [cache, urls, stableInput, store]);
1323
1346
  const loadedTextures = useLoader(
1324
1347
  TextureLoader,
1325
- IsObject(input) ? Object.values(input) : input
1348
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1326
1349
  );
1327
- const inputKey = urls.join("\0");
1328
1350
  useLayoutEffect(() => {
1329
1351
  if (cachedResult) return;
1330
1352
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1351,28 +1373,28 @@ function useTexture(input, optionsOrOnLoad) {
1351
1373
  }, [renderer, loadedTextures, cachedResult]);
1352
1374
  const mappedTextures = useMemo(() => {
1353
1375
  if (cachedResult) return cachedResult;
1354
- if (IsObject(input)) {
1376
+ if (IsObject(stableInput)) {
1355
1377
  const keyed = {};
1356
1378
  const textureArray = loadedTextures;
1357
1379
  let i = 0;
1358
- for (const key in input) keyed[key] = textureArray[i++];
1380
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1359
1381
  return keyed;
1360
1382
  } else {
1361
1383
  return loadedTextures;
1362
1384
  }
1363
- }, [input, loadedTextures, cachedResult]);
1385
+ }, [stableInput, loadedTextures, cachedResult]);
1364
1386
  useEffect(() => {
1365
1387
  if (!cache) return;
1366
1388
  const urlTextureMap = [];
1367
- if (typeof input === "string") {
1368
- urlTextureMap.push([input, mappedTextures]);
1369
- } else if (Array.isArray(input)) {
1389
+ if (typeof stableInput === "string") {
1390
+ urlTextureMap.push([stableInput, mappedTextures]);
1391
+ } else if (Array.isArray(stableInput)) {
1370
1392
  const textureArray = mappedTextures;
1371
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1372
- } else if (IsObject(input)) {
1393
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1394
+ } else if (IsObject(stableInput)) {
1373
1395
  const textureRecord = mappedTextures;
1374
- for (const key in input) {
1375
- const url = input[key];
1396
+ for (const key in stableInput) {
1397
+ const url = stableInput[key];
1376
1398
  urlTextureMap.push([url, textureRecord[key]]);
1377
1399
  }
1378
1400
  }
@@ -1401,7 +1423,7 @@ function useTexture(input, optionsOrOnLoad) {
1401
1423
  }
1402
1424
  return { _textureRefs: refs };
1403
1425
  });
1404
- }, [cache, input, mappedTextures, store]);
1426
+ }, [cache, stableInput, mappedTextures, store]);
1405
1427
  return mappedTextures;
1406
1428
  }
1407
1429
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1472,13 +1494,13 @@ function useTextures(selector) {
1472
1494
  };
1473
1495
  }, [store]);
1474
1496
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1475
- const selected = useThree(subscribe);
1497
+ const selected = useThree$1(subscribe);
1476
1498
  return selector ? selected : registry;
1477
1499
  }
1478
1500
 
1479
1501
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1480
- const isLegacy = useThree((s) => s.isLegacy);
1481
- const size = useThree((s) => s.size);
1502
+ const isLegacy = useThree$1((s) => s.isLegacy);
1503
+ const size = useThree$1((s) => s.size);
1482
1504
  let width;
1483
1505
  let height;
1484
1506
  let opts;
@@ -1535,7 +1557,7 @@ function useEnvironment({
1535
1557
  const { extension, isCubemap } = getExtension(files);
1536
1558
  const loader = getLoader(extension);
1537
1559
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1538
- const renderer = useThree((state) => state.renderer);
1560
+ const renderer = useThree$1((state) => state.renderer);
1539
1561
  useLayoutEffect(() => {
1540
1562
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1541
1563
  function clearGainmapTexture() {
@@ -1628,7 +1650,7 @@ function useStore() {
1628
1650
  if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1629
1651
  return store;
1630
1652
  }
1631
- function useThree(selector = (state) => state, equalityFn) {
1653
+ function useThree$1(selector = (state) => state, equalityFn) {
1632
1654
  return useStore()(selector, equalityFn);
1633
1655
  }
1634
1656
  function useInstanceHandle(ref) {
@@ -1670,13 +1692,19 @@ function addTail(callback) {
1670
1692
  return getScheduler().onIdle(callback);
1671
1693
  }
1672
1694
  function invalidate(state, frames = 1, stackFrames = false) {
1673
- getScheduler().invalidate(frames, stackFrames);
1695
+ const rootId = state?.internal?.rootId;
1696
+ const scheduler = getScheduler();
1697
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1698
+ else scheduler.invalidate(frames, stackFrames);
1674
1699
  }
1675
- function advance(timestamp) {
1676
- getScheduler().step(timestamp);
1700
+ function advance(timestamp, runGlobalEffects, state) {
1701
+ const rootId = state?.internal?.rootId;
1702
+ const scheduler = getScheduler();
1703
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1704
+ else scheduler.step(timestamp);
1677
1705
  }
1678
1706
 
1679
- const version = "10.0.0-alpha.3";
1707
+ const version = "10.0.0-alpha.4";
1680
1708
  const packageData = {
1681
1709
  version: version};
1682
1710
 
@@ -14317,7 +14345,7 @@ function createRoot(canvas) {
14317
14345
  const handleXRFrame = (timestamp, _frame) => {
14318
14346
  const state2 = store.getState();
14319
14347
  if (state2.frameloop === "never") return;
14320
- advance(timestamp);
14348
+ advance(timestamp, true, state2);
14321
14349
  };
14322
14350
  const actualRenderer = state.internal.actualRenderer;
14323
14351
  const handleSessionChange = () => {
@@ -14325,7 +14353,7 @@ function createRoot(canvas) {
14325
14353
  const renderer2 = state2.internal.actualRenderer;
14326
14354
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14327
14355
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14328
- if (!renderer2.xr.isPresenting) invalidate();
14356
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14329
14357
  };
14330
14358
  const xr = {
14331
14359
  connect() {
@@ -14407,7 +14435,11 @@ function createRoot(canvas) {
14407
14435
  const newRootId = canvasId || scheduler.generateRootId();
14408
14436
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14409
14437
  getState: () => store.getState(),
14410
- onError: (err) => store.getState().setError(err)
14438
+ onError: (err) => store.getState().setError(err),
14439
+ frameloop: store.getState().frameloop,
14440
+ before: schedulerConfig?.before,
14441
+ after: schedulerConfig?.after,
14442
+ order: schedulerConfig?.order
14411
14443
  });
14412
14444
  const unregisterCanvasTarget = scheduler.register(
14413
14445
  () => {
@@ -14415,6 +14447,10 @@ function createRoot(canvas) {
14415
14447
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14416
14448
  const renderer2 = state2.internal.actualRenderer;
14417
14449
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14450
+ if (state2.internal.canvasTargetSizeDirty) {
14451
+ state2.internal.canvasTargetSizeDirty = false;
14452
+ renderer2.backend?.updateSize?.();
14453
+ }
14418
14454
  }
14419
14455
  },
14420
14456
  {
@@ -14483,8 +14519,7 @@ function createRoot(canvas) {
14483
14519
  phase: "render",
14484
14520
  system: true,
14485
14521
  // Internal flag: this is a system job, not user-controlled
14486
- // Apply scheduler config for render ordering and rate limiting
14487
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14522
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14488
14523
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14489
14524
  }
14490
14525
  );
@@ -14503,8 +14538,21 @@ function createRoot(canvas) {
14503
14538
  scheduler
14504
14539
  }
14505
14540
  }));
14541
+ } else {
14542
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14543
+ if (constraintsChanged) {
14544
+ scheduler.setRootConstraints(rootId, {
14545
+ before: schedulerConfig?.before,
14546
+ after: schedulerConfig?.after
14547
+ });
14548
+ }
14549
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14550
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14551
+ }
14506
14552
  }
14507
- scheduler.frameloop = frameloop;
14553
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14554
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14555
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14508
14556
  onCreated = onCreatedCallback;
14509
14557
  configured = true;
14510
14558
  resolve();
@@ -14546,14 +14594,19 @@ function unmountComponentAtNode(canvas, callback) {
14546
14594
  const fiber = root?.fiber;
14547
14595
  if (fiber) {
14548
14596
  const state = root?.store.getState();
14549
- if (state) state.internal.active = false;
14597
+ if (state) {
14598
+ state.internal.active = false;
14599
+ const unregisterRoot = state.internal.unregisterRoot;
14600
+ if (unregisterRoot) {
14601
+ unregisterRoot();
14602
+ state.internal.unregisterRoot = void 0;
14603
+ }
14604
+ }
14550
14605
  reconciler.updateContainer(null, fiber, null, () => {
14551
14606
  if (state) {
14552
14607
  setTimeout(() => {
14553
14608
  try {
14554
14609
  const renderer = state.internal.actualRenderer;
14555
- const unregisterRoot = state.internal.unregisterRoot;
14556
- if (unregisterRoot) unregisterRoot();
14557
14610
  const unregisterPrimary = state.internal.unregisterPrimary;
14558
14611
  if (unregisterPrimary) unregisterPrimary();
14559
14612
  const canvasTarget = state.internal.canvasTarget;
@@ -14719,7 +14772,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14719
14772
  };
14720
14773
  }
14721
14774
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14722
- const defaultScene = useThree((state) => state.scene);
14775
+ const defaultScene = useThree$1((state) => state.scene);
14723
14776
  React.useLayoutEffect(() => {
14724
14777
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14725
14778
  });
@@ -14737,7 +14790,7 @@ function EnvironmentCube({
14737
14790
  ...rest
14738
14791
  }) {
14739
14792
  const texture = useEnvironment(rest);
14740
- const defaultScene = useThree((state) => state.scene);
14793
+ const defaultScene = useThree$1((state) => state.scene);
14741
14794
  React.useLayoutEffect(() => {
14742
14795
  return setEnvProps(background, scene, defaultScene, texture, {
14743
14796
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14774,9 +14827,9 @@ function EnvironmentPortal({
14774
14827
  preset = void 0,
14775
14828
  extensions
14776
14829
  }) {
14777
- const renderer = useThree((state) => state.renderer);
14778
- const isLegacy = useThree((state) => state.isLegacy);
14779
- const defaultScene = useThree((state) => state.scene);
14830
+ const renderer = useThree$1((state) => state.renderer);
14831
+ const isLegacy = useThree$1((state) => state.isLegacy);
14832
+ const defaultScene = useThree$1((state) => state.scene);
14780
14833
  const camera = React.useRef(null);
14781
14834
  const [virtualScene] = React.useState(() => new Scene());
14782
14835
  const fbo = React.useMemo(() => {
@@ -14820,7 +14873,7 @@ function EnvironmentPortal({
14820
14873
  environmentRotation
14821
14874
  ]);
14822
14875
  let count = 1;
14823
- useFrame(() => {
14876
+ useFrame$1(() => {
14824
14877
  if (frames === Infinity || count < frames) {
14825
14878
  const autoClear = renderer.autoClear;
14826
14879
  renderer.autoClear = true;
@@ -14860,7 +14913,7 @@ function EnvironmentGround(props) {
14860
14913
  ] });
14861
14914
  }
14862
14915
  function EnvironmentColor({ color, scene }) {
14863
- const defaultScene = useThree((state) => state.scene);
14916
+ const defaultScene = useThree$1((state) => state.scene);
14864
14917
  React.useLayoutEffect(() => {
14865
14918
  if (color === void 0) return;
14866
14919
  const target = resolveScene(scene || defaultScene);
@@ -15098,9 +15151,9 @@ function CanvasImpl({
15098
15151
  React.useMemo(() => extend(THREE), []);
15099
15152
  const Bridge = useBridge();
15100
15153
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15101
- const hasInitialSizeRef = React.useRef(false);
15154
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15102
15155
  const measureConfig = React.useMemo(() => {
15103
- if (!hasInitialSizeRef.current) {
15156
+ if (!hasInitialSize) {
15104
15157
  return {
15105
15158
  ...resize,
15106
15159
  scroll: resize?.scroll ?? true,
@@ -15112,7 +15165,7 @@ function CanvasImpl({
15112
15165
  debounce: { scroll: 50, resize: 0 },
15113
15166
  ...resize
15114
15167
  };
15115
- }, [resize, hasInitialSizeRef.current]);
15168
+ }, [hasInitialSize, resize]);
15116
15169
  const [containerRef, containerRect] = useMeasure(measureConfig);
15117
15170
  const effectiveSize = React.useMemo(() => {
15118
15171
  let w = width ?? containerRect.width;
@@ -15128,9 +15181,7 @@ function CanvasImpl({
15128
15181
  left: containerRect.left
15129
15182
  };
15130
15183
  }, [width, height, containerRect, forceEven]);
15131
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15132
- hasInitialSizeRef.current = true;
15133
- }
15184
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15134
15185
  const canvasRef = React.useRef(null);
15135
15186
  const divRef = React.useRef(null);
15136
15187
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15244,6 +15295,7 @@ function CanvasImpl({
15244
15295
  const canvas = canvasRef.current;
15245
15296
  if (canvas) {
15246
15297
  return () => {
15298
+ if (canvas.isConnected) return;
15247
15299
  unmountComponentAtNode(canvas);
15248
15300
  root.current = null;
15249
15301
  };
@@ -15454,6 +15506,11 @@ function createTextureOperations(set) {
15454
15506
  removeMultiple: (keys) => removeTextures(set, keys)
15455
15507
  };
15456
15508
  }
15509
+ function scopedNodeName(scope, name) {
15510
+ const raw = scope ? `${scope}_${name}` : name;
15511
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15512
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15513
+ }
15457
15514
  function extractTSLValue(value) {
15458
15515
  if (value === null || value === void 0) return value;
15459
15516
  if (typeof value !== "object") return value;
@@ -15652,8 +15709,7 @@ function createUniform(inName, node, scope) {
15652
15709
  const inValue = vectorize(node);
15653
15710
  const newUniform = uniform(inValue);
15654
15711
  if (typeof newUniform.setName === "function") {
15655
- const name = scope ? `${scope}_${inName}` : inName;
15656
- newUniform.setName(name);
15712
+ newUniform.setName(scopedNodeName(scope, inName));
15657
15713
  }
15658
15714
  return newUniform;
15659
15715
  }
@@ -15752,7 +15808,7 @@ function useNodes(creatorOrScope, scope) {
15752
15808
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15753
15809
  },
15754
15810
  prepare: (name, node) => {
15755
- node.setName?.(scope ? `${scope}.${name}` : name);
15811
+ node.setName?.(scopedNodeName(scope, name));
15756
15812
  return node;
15757
15813
  }
15758
15814
  });
@@ -15861,7 +15917,7 @@ function useBuffers(creatorOrScope, scope) {
15861
15917
  },
15862
15918
  prepare: (name, buffer) => {
15863
15919
  if ("setName" in buffer && typeof buffer.setName === "function") {
15864
- buffer.setName(scope ? `${scope}.${name}` : name);
15920
+ buffer.setName(scopedNodeName(scope, name));
15865
15921
  }
15866
15922
  return buffer;
15867
15923
  }
@@ -15932,7 +15988,7 @@ function useGPUStorage(creatorOrScope, scope) {
15932
15988
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15933
15989
  },
15934
15990
  prepare: (name, storage) => {
15935
- const label = scope ? `${scope}.${name}` : name;
15991
+ const label = scopedNodeName(scope, name);
15936
15992
  if ("setName" in storage && typeof storage.setName === "function") {
15937
15993
  storage.setName(label);
15938
15994
  }
@@ -15963,9 +16019,10 @@ function rebuildAllStorage(store, scope) {
15963
16019
 
15964
16020
  function useRenderPipeline(mainCB, setupCB) {
15965
16021
  const store = useStore();
15966
- const { scene, camera, renderer, isLegacy } = useThree();
16022
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15967
16023
  const callbacksRanRef = useRef(false);
15968
16024
  const scenePassCacheRef = useRef(null);
16025
+ const forceScenePassRebuildRef = useRef(false);
15969
16026
  const mainCBRef = useRef(mainCB);
15970
16027
  const setupCBRef = useRef(setupCB);
15971
16028
  mainCBRef.current = mainCB;
@@ -15973,7 +16030,7 @@ function useRenderPipeline(mainCB, setupCB) {
15973
16030
  const [rebuildVersion, setRebuildVersion] = useState(0);
15974
16031
  useEffect(() => {
15975
16032
  callbacksRanRef.current = false;
15976
- scenePassCacheRef.current = null;
16033
+ forceScenePassRebuildRef.current = true;
15977
16034
  }, []);
15978
16035
  const clearPasses = useCallback(() => {
15979
16036
  store.setState({ passes: {} });
@@ -15983,12 +16040,14 @@ function useRenderPipeline(mainCB, setupCB) {
15983
16040
  renderPipeline: null,
15984
16041
  passes: {}
15985
16042
  });
16043
+ scenePassCacheRef.current?.scenePass?.dispose?.();
15986
16044
  callbacksRanRef.current = false;
15987
16045
  scenePassCacheRef.current = null;
16046
+ forceScenePassRebuildRef.current = false;
15988
16047
  }, [store]);
15989
16048
  const rebuild = useCallback(() => {
15990
16049
  callbacksRanRef.current = false;
15991
- scenePassCacheRef.current = null;
16050
+ forceScenePassRebuildRef.current = true;
15992
16051
  setRebuildVersion((v) => v + 1);
15993
16052
  }, []);
15994
16053
  useLayoutEffect(() => {
@@ -16006,21 +16065,31 @@ function useRenderPipeline(mainCB, setupCB) {
16006
16065
  pipeline = new RenderPipeline(renderer);
16007
16066
  justCreatedPipeline = true;
16008
16067
  }
16009
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16068
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16069
+ let outgoingScenePass = null;
16010
16070
  let scenePass;
16011
16071
  if (cacheValid) {
16012
16072
  scenePass = scenePassCacheRef.current.scenePass;
16013
16073
  } else {
16074
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16014
16075
  scenePass = pass(scene, camera);
16015
16076
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16077
+ forceScenePassRebuildRef.current = false;
16016
16078
  }
16017
16079
  currentPasses.scenePass = scenePass;
16018
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16080
+ let outputNodeChanged = false;
16081
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16082
+ pipeline.outputNode = scenePass;
16083
+ outputNodeChanged = true;
16084
+ }
16019
16085
  set({ renderPipeline: pipeline, passes: currentPasses });
16020
16086
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16021
16087
  if (shouldRunCallbacks) {
16022
16088
  if (setupCBRef.current) {
16023
- const freshState = store.getState();
16089
+ const freshState = Object.assign({}, store.getState(), {
16090
+ renderPipeline: pipeline,
16091
+ passes: currentPasses
16092
+ });
16024
16093
  const setupResult = setupCBRef.current(freshState);
16025
16094
  if (setupResult && typeof setupResult === "object") {
16026
16095
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16028,21 +16097,27 @@ function useRenderPipeline(mainCB, setupCB) {
16028
16097
  }
16029
16098
  }
16030
16099
  if (mainCBRef.current) {
16031
- const freshState = store.getState();
16100
+ const freshState = Object.assign({}, store.getState(), {
16101
+ renderPipeline: pipeline,
16102
+ passes: currentPasses
16103
+ });
16032
16104
  const mainResult = mainCBRef.current(freshState);
16033
16105
  if (mainResult && typeof mainResult === "object") {
16034
16106
  currentPasses = { ...currentPasses, ...mainResult };
16035
16107
  set({ passes: currentPasses });
16036
16108
  }
16037
16109
  }
16110
+ outputNodeChanged = true;
16038
16111
  callbacksRanRef.current = true;
16039
16112
  }
16113
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16114
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose?.();
16040
16115
  } catch (error) {
16041
16116
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16042
16117
  }
16043
16118
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16044
- const passes = useThree((s) => s.passes);
16045
- const renderPipeline = useThree((s) => s.renderPipeline);
16119
+ const passes = useThree$1((s) => s.passes);
16120
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16046
16121
  return {
16047
16122
  passes,
16048
16123
  renderPipeline,
@@ -16055,5 +16130,7 @@ function useRenderPipeline(mainCB, setupCB) {
16055
16130
  }
16056
16131
 
16057
16132
  extend(THREE);
16133
+ const useThree = useThree$1;
16134
+ const useFrame = useFrame$1;
16058
16135
 
16059
16136
  export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useBuffers, useEnvironment, useFrame, useGPUStorage, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, useRenderPipeline, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/fiber",
3
- "version": "10.0.0-canary.c6846c3",
3
+ "version": "10.0.0-canary.cdaab7c",
4
4
  "description": "A React renderer for Threejs",
5
5
  "keywords": [
6
6
  "react",
@@ -58,7 +58,7 @@
58
58
  "dependencies": {
59
59
  "@babel/runtime": "^7.28.6",
60
60
  "@monogrid/gainmap-js": "^3.4.0",
61
- "@pmndrs/scheduler": "^0.1.0",
61
+ "@pmndrs/scheduler": "^0.2.0",
62
62
  "dequal": "^2.0.3",
63
63
  "its-fine": "^2.0.0",
64
64
  "react-use-measure": "^2.1.7",