@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.
package/dist/legacy.cjs CHANGED
@@ -50,11 +50,9 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
50
50
 
51
51
  const R3F_BUILD_LEGACY = true;
52
52
  const R3F_BUILD_WEBGPU = false;
53
- const Inspector = class Inspector2 {
54
- constructor() {
55
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
56
- }
57
- };
53
+ async function loadInspector() {
54
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
55
+ }
58
56
  const WebGPURenderer = class WebGPURenderer2 {
59
57
  constructor() {
60
58
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
@@ -67,12 +65,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
67
65
  __proto__: null,
68
66
  CubeRenderTarget: CubeRenderTarget,
69
67
  CubeRenderTargetCompat: three.WebGLCubeRenderTarget,
70
- Inspector: Inspector,
71
68
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
69
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
70
  RenderTarget: RenderTarget,
74
71
  RenderTargetCompat: three.WebGLRenderTarget,
75
- WebGPURenderer: WebGPURenderer
72
+ WebGPURenderer: WebGPURenderer,
73
+ loadInspector: loadInspector
76
74
  }, [three__namespace]);
77
75
 
78
76
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -628,7 +626,12 @@ function diffProps(instance, newProps) {
628
626
  const changedProps = {};
629
627
  for (const prop in newProps) {
630
628
  if (RESERVED_PROPS.includes(prop)) continue;
631
- if (is.equ(newProps[prop], instance.props[prop])) continue;
629
+ if (is.equ(newProps[prop], instance.props[prop])) {
630
+ const value = newProps[prop];
631
+ if (!isCopyable(value)) continue;
632
+ const { target } = resolve(instance.object, prop);
633
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
634
+ }
632
635
  changedProps[prop] = newProps[prop];
633
636
  for (const other in newProps) {
634
637
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -931,7 +934,7 @@ const createStore = (invalidate, advance) => {
931
934
  size: newSize,
932
935
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
933
936
  }));
934
- scheduler.getScheduler().invalidate();
937
+ get().invalidate();
935
938
  }
936
939
  }
937
940
  return;
@@ -946,15 +949,16 @@ const createStore = (invalidate, advance) => {
946
949
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
947
950
  _sizeImperative: true
948
951
  }));
949
- scheduler.getScheduler().invalidate();
952
+ get().invalidate();
950
953
  },
951
954
  setDpr: (dpr) => set((state2) => {
952
955
  const resolved = calculateDpr(dpr);
953
956
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
954
957
  }),
955
958
  setFrameloop: (frameloop = "always") => {
959
+ const rootId = get().internal.rootId;
960
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
956
961
  set(() => ({ frameloop }));
957
- scheduler.getScheduler().frameloop = frameloop;
958
962
  },
959
963
  setError: (error) => set(() => ({ error })),
960
964
  error: null,
@@ -1070,17 +1074,11 @@ const createStore = (invalidate, advance) => {
1070
1074
  oldSize = size;
1071
1075
  oldDpr = viewport.dpr;
1072
1076
  updateCamera(camera, size);
1073
- if (internal.isSecondary && canvasTarget) {
1074
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1075
- canvasTarget.setSize(size.width, size.height, false);
1076
- } else {
1077
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1078
- actualRenderer.setSize(size.width, size.height, false);
1079
- if (canvasTarget) {
1080
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1081
- canvasTarget.setSize(size.width, size.height, false);
1082
- }
1083
- }
1077
+ const resizeTarget = canvasTarget ?? actualRenderer;
1078
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1079
+ resizeTarget.setSize(size.width, size.height, false);
1080
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1081
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1084
1082
  }
1085
1083
  if (camera !== oldCamera) {
1086
1084
  oldCamera = camera;
@@ -1142,7 +1140,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1142
1140
  const results = cacheKeys.map(
1143
1141
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1144
1142
  );
1145
- 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];
1146
1148
  }
1147
1149
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1148
1150
  const inputs = Array.isArray(input) ? input : [input];
@@ -1262,6 +1264,13 @@ function useFrame(callback, priorityOrOptions) {
1262
1264
  * Use for controlling the entire frame loop, adding phases, etc.
1263
1265
  */
1264
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
+ },
1265
1274
  /**
1266
1275
  * Manually step this job only.
1267
1276
  * Bypasses FPS limiting - always runs.
@@ -1278,6 +1287,14 @@ function useFrame(callback, priorityOrOptions) {
1278
1287
  stepAll: (timestamp) => {
1279
1288
  scheduler.getScheduler().step(timestamp);
1280
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
+ },
1281
1298
  /**
1282
1299
  * Pause this job (set enabled=false).
1283
1300
  * Job remains registered but won't run.
@@ -1307,6 +1324,11 @@ function getUrls(input) {
1307
1324
  if (Array.isArray(input)) return input;
1308
1325
  return Object.values(input);
1309
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
+ }
1310
1332
  function allUrlsCached(urls, textureCache) {
1311
1333
  return urls.every((url) => textureCache.has(url));
1312
1334
  }
@@ -1332,18 +1354,19 @@ function useTexture(input, optionsOrOnLoad) {
1332
1354
  const onLoadRef = React.useRef(onLoad);
1333
1355
  onLoadRef.current = onLoad;
1334
1356
  const onLoadCalledForRef = React.useRef(null);
1335
- 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]);
1336
1360
  const cachedResult = React.useMemo(() => {
1337
1361
  if (!cache) return null;
1338
1362
  const textures = store.getState().textures;
1339
1363
  if (!allUrlsCached(urls, textures)) return null;
1340
- return buildFromCache(input, textures);
1341
- }, [cache, urls, input, store]);
1364
+ return buildFromCache(stableInput, textures);
1365
+ }, [cache, urls, stableInput, store]);
1342
1366
  const loadedTextures = useLoader(
1343
1367
  three.TextureLoader,
1344
- IsObject(input) ? Object.values(input) : input
1368
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1345
1369
  );
1346
- const inputKey = urls.join("\0");
1347
1370
  React.useLayoutEffect(() => {
1348
1371
  if (cachedResult) return;
1349
1372
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1370,28 +1393,28 @@ function useTexture(input, optionsOrOnLoad) {
1370
1393
  }, [renderer, loadedTextures, cachedResult]);
1371
1394
  const mappedTextures = React.useMemo(() => {
1372
1395
  if (cachedResult) return cachedResult;
1373
- if (IsObject(input)) {
1396
+ if (IsObject(stableInput)) {
1374
1397
  const keyed = {};
1375
1398
  const textureArray = loadedTextures;
1376
1399
  let i = 0;
1377
- for (const key in input) keyed[key] = textureArray[i++];
1400
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1378
1401
  return keyed;
1379
1402
  } else {
1380
1403
  return loadedTextures;
1381
1404
  }
1382
- }, [input, loadedTextures, cachedResult]);
1405
+ }, [stableInput, loadedTextures, cachedResult]);
1383
1406
  React.useEffect(() => {
1384
1407
  if (!cache) return;
1385
1408
  const urlTextureMap = [];
1386
- if (typeof input === "string") {
1387
- urlTextureMap.push([input, mappedTextures]);
1388
- } else if (Array.isArray(input)) {
1409
+ if (typeof stableInput === "string") {
1410
+ urlTextureMap.push([stableInput, mappedTextures]);
1411
+ } else if (Array.isArray(stableInput)) {
1389
1412
  const textureArray = mappedTextures;
1390
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1391
- } else if (IsObject(input)) {
1413
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1414
+ } else if (IsObject(stableInput)) {
1392
1415
  const textureRecord = mappedTextures;
1393
- for (const key in input) {
1394
- const url = input[key];
1416
+ for (const key in stableInput) {
1417
+ const url = stableInput[key];
1395
1418
  urlTextureMap.push([url, textureRecord[key]]);
1396
1419
  }
1397
1420
  }
@@ -1420,7 +1443,7 @@ function useTexture(input, optionsOrOnLoad) {
1420
1443
  }
1421
1444
  return { _textureRefs: refs };
1422
1445
  });
1423
- }, [cache, input, mappedTextures, store]);
1446
+ }, [cache, stableInput, mappedTextures, store]);
1424
1447
  return mappedTextures;
1425
1448
  }
1426
1449
  useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
@@ -1689,13 +1712,19 @@ function addTail(callback) {
1689
1712
  return scheduler.getScheduler().onIdle(callback);
1690
1713
  }
1691
1714
  function invalidate(state, frames = 1, stackFrames = false) {
1692
- 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);
1693
1719
  }
1694
- function advance(timestamp) {
1695
- 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);
1696
1725
  }
1697
1726
 
1698
- const version = "10.0.0-alpha.3";
1727
+ const version = "10.0.0-alpha.4";
1699
1728
  const packageData = {
1700
1729
  version: version};
1701
1730
 
@@ -14307,7 +14336,7 @@ function createRoot(canvas) {
14307
14336
  const handleXRFrame = (timestamp, _frame) => {
14308
14337
  const state2 = store.getState();
14309
14338
  if (state2.frameloop === "never") return;
14310
- advance(timestamp);
14339
+ advance(timestamp, true, state2);
14311
14340
  };
14312
14341
  const actualRenderer = state.internal.actualRenderer;
14313
14342
  const handleSessionChange = () => {
@@ -14315,7 +14344,7 @@ function createRoot(canvas) {
14315
14344
  const renderer2 = state2.internal.actualRenderer;
14316
14345
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14317
14346
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14318
- if (!renderer2.xr.isPresenting) invalidate();
14347
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14319
14348
  };
14320
14349
  const xr = {
14321
14350
  connect() {
@@ -14397,14 +14426,23 @@ function createRoot(canvas) {
14397
14426
  const newRootId = canvasId || scheduler$1.generateRootId();
14398
14427
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14399
14428
  getState: () => store.getState(),
14400
- onError: (err) => store.getState().setError(err)
14429
+ onError: (err) => store.getState().setError(err),
14430
+ frameloop: store.getState().frameloop,
14431
+ before: schedulerConfig?.before,
14432
+ after: schedulerConfig?.after,
14433
+ order: schedulerConfig?.order
14401
14434
  });
14402
14435
  const unregisterCanvasTarget = scheduler$1.register(
14403
14436
  () => {
14404
14437
  const state2 = store.getState();
14405
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14438
+ const canvasTarget = state2.internal.canvasTarget;
14439
+ if (canvasTarget) {
14406
14440
  const renderer2 = state2.internal.actualRenderer;
14407
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14441
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14442
+ if (state2.internal.canvasTargetSizeDirty) {
14443
+ state2.internal.canvasTargetSizeDirty = false;
14444
+ renderer2.backend?.updateSize?.();
14445
+ }
14408
14446
  }
14409
14447
  },
14410
14448
  {
@@ -14473,8 +14511,7 @@ function createRoot(canvas) {
14473
14511
  phase: "render",
14474
14512
  system: true,
14475
14513
  // Internal flag: this is a system job, not user-controlled
14476
- // Apply scheduler config for render ordering and rate limiting
14477
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14514
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14478
14515
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14479
14516
  }
14480
14517
  );
@@ -14493,8 +14530,21 @@ function createRoot(canvas) {
14493
14530
  scheduler: scheduler$1
14494
14531
  }
14495
14532
  }));
14533
+ } else {
14534
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14535
+ if (constraintsChanged) {
14536
+ scheduler$1.setRootConstraints(rootId, {
14537
+ before: schedulerConfig?.before,
14538
+ after: schedulerConfig?.after
14539
+ });
14540
+ }
14541
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14542
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14543
+ }
14496
14544
  }
14497
- scheduler$1.frameloop = frameloop;
14545
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14546
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14547
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14498
14548
  onCreated = onCreatedCallback;
14499
14549
  configured = true;
14500
14550
  resolve();
@@ -14536,18 +14586,23 @@ function unmountComponentAtNode(canvas, callback) {
14536
14586
  const fiber = root?.fiber;
14537
14587
  if (fiber) {
14538
14588
  const state = root?.store.getState();
14539
- if (state) state.internal.active = false;
14589
+ if (state) {
14590
+ state.internal.active = false;
14591
+ const unregisterRoot = state.internal.unregisterRoot;
14592
+ if (unregisterRoot) {
14593
+ unregisterRoot();
14594
+ state.internal.unregisterRoot = void 0;
14595
+ }
14596
+ }
14540
14597
  reconciler.updateContainer(null, fiber, null, () => {
14541
14598
  if (state) {
14542
14599
  setTimeout(() => {
14543
14600
  try {
14544
14601
  const renderer = state.internal.actualRenderer;
14545
- const unregisterRoot = state.internal.unregisterRoot;
14546
- if (unregisterRoot) unregisterRoot();
14547
14602
  const unregisterPrimary = state.internal.unregisterPrimary;
14548
14603
  if (unregisterPrimary) unregisterPrimary();
14549
14604
  const canvasTarget = state.internal.canvasTarget;
14550
- if (canvasTarget?.dispose) canvasTarget.dispose();
14605
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14551
14606
  state.events.disconnect?.();
14552
14607
  cleanupHelperGroup(root.store);
14553
14608
  if (state.isLegacy && renderer) {
@@ -14991,9 +15046,9 @@ function CanvasImpl({
14991
15046
  React__namespace.useMemo(() => extend(THREE), []);
14992
15047
  const Bridge = useBridge();
14993
15048
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
14994
- const hasInitialSizeRef = React__namespace.useRef(false);
15049
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
14995
15050
  const measureConfig = React__namespace.useMemo(() => {
14996
- if (!hasInitialSizeRef.current) {
15051
+ if (!hasInitialSize) {
14997
15052
  return {
14998
15053
  ...resize,
14999
15054
  scroll: resize?.scroll ?? true,
@@ -15005,7 +15060,7 @@ function CanvasImpl({
15005
15060
  debounce: { scroll: 50, resize: 0 },
15006
15061
  ...resize
15007
15062
  };
15008
- }, [resize, hasInitialSizeRef.current]);
15063
+ }, [hasInitialSize, resize]);
15009
15064
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15010
15065
  const effectiveSize = React__namespace.useMemo(() => {
15011
15066
  let w = width ?? containerRect.width;
@@ -15021,9 +15076,7 @@ function CanvasImpl({
15021
15076
  left: containerRect.left
15022
15077
  };
15023
15078
  }, [width, height, containerRect, forceEven]);
15024
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15025
- hasInitialSizeRef.current = true;
15026
- }
15079
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15027
15080
  const canvasRef = React__namespace.useRef(null);
15028
15081
  const divRef = React__namespace.useRef(null);
15029
15082
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15137,6 +15190,7 @@ function CanvasImpl({
15137
15190
  const canvas = canvasRef.current;
15138
15191
  if (canvas) {
15139
15192
  return () => {
15193
+ if (canvas.isConnected) return;
15140
15194
  unmountComponentAtNode(canvas);
15141
15195
  root.current = null;
15142
15196
  };