@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.mjs CHANGED
@@ -30,11 +30,9 @@ function _mergeNamespaces(n, m) {
30
30
 
31
31
  const R3F_BUILD_LEGACY = true;
32
32
  const R3F_BUILD_WEBGPU = false;
33
- const Inspector = class Inspector2 {
34
- constructor() {
35
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
36
- }
37
- };
33
+ async function loadInspector() {
34
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
35
+ }
38
36
  const WebGPURenderer = class WebGPURenderer2 {
39
37
  constructor() {
40
38
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
@@ -47,12 +45,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
47
45
  __proto__: null,
48
46
  CubeRenderTarget: CubeRenderTarget,
49
47
  CubeRenderTargetCompat: WebGLCubeRenderTarget,
50
- Inspector: Inspector,
51
48
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
52
49
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
53
50
  RenderTarget: RenderTarget,
54
51
  RenderTargetCompat: WebGLRenderTarget,
55
- WebGPURenderer: WebGPURenderer
52
+ WebGPURenderer: WebGPURenderer,
53
+ loadInspector: loadInspector
56
54
  }, [three]);
57
55
 
58
56
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -608,7 +606,12 @@ function diffProps(instance, newProps) {
608
606
  const changedProps = {};
609
607
  for (const prop in newProps) {
610
608
  if (RESERVED_PROPS.includes(prop)) continue;
611
- if (is.equ(newProps[prop], instance.props[prop])) continue;
609
+ if (is.equ(newProps[prop], instance.props[prop])) {
610
+ const value = newProps[prop];
611
+ if (!isCopyable(value)) continue;
612
+ const { target } = resolve(instance.object, prop);
613
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
614
+ }
612
615
  changedProps[prop] = newProps[prop];
613
616
  for (const other in newProps) {
614
617
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -911,7 +914,7 @@ const createStore = (invalidate, advance) => {
911
914
  size: newSize,
912
915
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
913
916
  }));
914
- getScheduler().invalidate();
917
+ get().invalidate();
915
918
  }
916
919
  }
917
920
  return;
@@ -926,15 +929,16 @@ const createStore = (invalidate, advance) => {
926
929
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
927
930
  _sizeImperative: true
928
931
  }));
929
- getScheduler().invalidate();
932
+ get().invalidate();
930
933
  },
931
934
  setDpr: (dpr) => set((state2) => {
932
935
  const resolved = calculateDpr(dpr);
933
936
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
934
937
  }),
935
938
  setFrameloop: (frameloop = "always") => {
939
+ const rootId = get().internal.rootId;
940
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
936
941
  set(() => ({ frameloop }));
937
- getScheduler().frameloop = frameloop;
938
942
  },
939
943
  setError: (error) => set(() => ({ error })),
940
944
  error: null,
@@ -1050,17 +1054,11 @@ const createStore = (invalidate, advance) => {
1050
1054
  oldSize = size;
1051
1055
  oldDpr = viewport.dpr;
1052
1056
  updateCamera(camera, size);
1053
- if (internal.isSecondary && canvasTarget) {
1054
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1055
- canvasTarget.setSize(size.width, size.height, false);
1056
- } else {
1057
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1058
- actualRenderer.setSize(size.width, size.height, false);
1059
- if (canvasTarget) {
1060
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1061
- canvasTarget.setSize(size.width, size.height, false);
1062
- }
1063
- }
1057
+ const resizeTarget = canvasTarget ?? actualRenderer;
1058
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1059
+ resizeTarget.setSize(size.width, size.height, false);
1060
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1061
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1064
1062
  }
1065
1063
  if (camera !== oldCamera) {
1066
1064
  oldCamera = camera;
@@ -1122,7 +1120,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1122
1120
  const results = cacheKeys.map(
1123
1121
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1124
1122
  );
1125
- 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];
1126
1128
  }
1127
1129
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1128
1130
  const inputs = Array.isArray(input) ? input : [input];
@@ -1242,6 +1244,13 @@ function useFrame(callback, priorityOrOptions) {
1242
1244
  * Use for controlling the entire frame loop, adding phases, etc.
1243
1245
  */
1244
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
+ },
1245
1254
  /**
1246
1255
  * Manually step this job only.
1247
1256
  * Bypasses FPS limiting - always runs.
@@ -1258,6 +1267,14 @@ function useFrame(callback, priorityOrOptions) {
1258
1267
  stepAll: (timestamp) => {
1259
1268
  getScheduler().step(timestamp);
1260
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
+ },
1261
1278
  /**
1262
1279
  * Pause this job (set enabled=false).
1263
1280
  * Job remains registered but won't run.
@@ -1287,6 +1304,11 @@ function getUrls(input) {
1287
1304
  if (Array.isArray(input)) return input;
1288
1305
  return Object.values(input);
1289
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
+ }
1290
1312
  function allUrlsCached(urls, textureCache) {
1291
1313
  return urls.every((url) => textureCache.has(url));
1292
1314
  }
@@ -1312,18 +1334,19 @@ function useTexture(input, optionsOrOnLoad) {
1312
1334
  const onLoadRef = useRef(onLoad);
1313
1335
  onLoadRef.current = onLoad;
1314
1336
  const onLoadCalledForRef = useRef(null);
1315
- 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]);
1316
1340
  const cachedResult = useMemo(() => {
1317
1341
  if (!cache) return null;
1318
1342
  const textures = store.getState().textures;
1319
1343
  if (!allUrlsCached(urls, textures)) return null;
1320
- return buildFromCache(input, textures);
1321
- }, [cache, urls, input, store]);
1344
+ return buildFromCache(stableInput, textures);
1345
+ }, [cache, urls, stableInput, store]);
1322
1346
  const loadedTextures = useLoader(
1323
1347
  TextureLoader,
1324
- IsObject(input) ? Object.values(input) : input
1348
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1325
1349
  );
1326
- const inputKey = urls.join("\0");
1327
1350
  useLayoutEffect(() => {
1328
1351
  if (cachedResult) return;
1329
1352
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1350,28 +1373,28 @@ function useTexture(input, optionsOrOnLoad) {
1350
1373
  }, [renderer, loadedTextures, cachedResult]);
1351
1374
  const mappedTextures = useMemo(() => {
1352
1375
  if (cachedResult) return cachedResult;
1353
- if (IsObject(input)) {
1376
+ if (IsObject(stableInput)) {
1354
1377
  const keyed = {};
1355
1378
  const textureArray = loadedTextures;
1356
1379
  let i = 0;
1357
- for (const key in input) keyed[key] = textureArray[i++];
1380
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1358
1381
  return keyed;
1359
1382
  } else {
1360
1383
  return loadedTextures;
1361
1384
  }
1362
- }, [input, loadedTextures, cachedResult]);
1385
+ }, [stableInput, loadedTextures, cachedResult]);
1363
1386
  useEffect(() => {
1364
1387
  if (!cache) return;
1365
1388
  const urlTextureMap = [];
1366
- if (typeof input === "string") {
1367
- urlTextureMap.push([input, mappedTextures]);
1368
- } else if (Array.isArray(input)) {
1389
+ if (typeof stableInput === "string") {
1390
+ urlTextureMap.push([stableInput, mappedTextures]);
1391
+ } else if (Array.isArray(stableInput)) {
1369
1392
  const textureArray = mappedTextures;
1370
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1371
- } else if (IsObject(input)) {
1393
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1394
+ } else if (IsObject(stableInput)) {
1372
1395
  const textureRecord = mappedTextures;
1373
- for (const key in input) {
1374
- const url = input[key];
1396
+ for (const key in stableInput) {
1397
+ const url = stableInput[key];
1375
1398
  urlTextureMap.push([url, textureRecord[key]]);
1376
1399
  }
1377
1400
  }
@@ -1400,7 +1423,7 @@ function useTexture(input, optionsOrOnLoad) {
1400
1423
  }
1401
1424
  return { _textureRefs: refs };
1402
1425
  });
1403
- }, [cache, input, mappedTextures, store]);
1426
+ }, [cache, stableInput, mappedTextures, store]);
1404
1427
  return mappedTextures;
1405
1428
  }
1406
1429
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1669,13 +1692,19 @@ function addTail(callback) {
1669
1692
  return getScheduler().onIdle(callback);
1670
1693
  }
1671
1694
  function invalidate(state, frames = 1, stackFrames = false) {
1672
- 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);
1673
1699
  }
1674
- function advance(timestamp) {
1675
- 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);
1676
1705
  }
1677
1706
 
1678
- const version = "10.0.0-alpha.3";
1707
+ const version = "10.0.0-alpha.4";
1679
1708
  const packageData = {
1680
1709
  version: version};
1681
1710
 
@@ -14287,7 +14316,7 @@ function createRoot(canvas) {
14287
14316
  const handleXRFrame = (timestamp, _frame) => {
14288
14317
  const state2 = store.getState();
14289
14318
  if (state2.frameloop === "never") return;
14290
- advance(timestamp);
14319
+ advance(timestamp, true, state2);
14291
14320
  };
14292
14321
  const actualRenderer = state.internal.actualRenderer;
14293
14322
  const handleSessionChange = () => {
@@ -14295,7 +14324,7 @@ function createRoot(canvas) {
14295
14324
  const renderer2 = state2.internal.actualRenderer;
14296
14325
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14297
14326
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14298
- if (!renderer2.xr.isPresenting) invalidate();
14327
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14299
14328
  };
14300
14329
  const xr = {
14301
14330
  connect() {
@@ -14377,14 +14406,23 @@ function createRoot(canvas) {
14377
14406
  const newRootId = canvasId || scheduler.generateRootId();
14378
14407
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14379
14408
  getState: () => store.getState(),
14380
- onError: (err) => store.getState().setError(err)
14409
+ onError: (err) => store.getState().setError(err),
14410
+ frameloop: store.getState().frameloop,
14411
+ before: schedulerConfig?.before,
14412
+ after: schedulerConfig?.after,
14413
+ order: schedulerConfig?.order
14381
14414
  });
14382
14415
  const unregisterCanvasTarget = scheduler.register(
14383
14416
  () => {
14384
14417
  const state2 = store.getState();
14385
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14418
+ const canvasTarget = state2.internal.canvasTarget;
14419
+ if (canvasTarget) {
14386
14420
  const renderer2 = state2.internal.actualRenderer;
14387
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14421
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14422
+ if (state2.internal.canvasTargetSizeDirty) {
14423
+ state2.internal.canvasTargetSizeDirty = false;
14424
+ renderer2.backend?.updateSize?.();
14425
+ }
14388
14426
  }
14389
14427
  },
14390
14428
  {
@@ -14453,8 +14491,7 @@ function createRoot(canvas) {
14453
14491
  phase: "render",
14454
14492
  system: true,
14455
14493
  // Internal flag: this is a system job, not user-controlled
14456
- // Apply scheduler config for render ordering and rate limiting
14457
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14494
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14458
14495
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14459
14496
  }
14460
14497
  );
@@ -14473,8 +14510,21 @@ function createRoot(canvas) {
14473
14510
  scheduler
14474
14511
  }
14475
14512
  }));
14513
+ } else {
14514
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14515
+ if (constraintsChanged) {
14516
+ scheduler.setRootConstraints(rootId, {
14517
+ before: schedulerConfig?.before,
14518
+ after: schedulerConfig?.after
14519
+ });
14520
+ }
14521
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14522
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14523
+ }
14476
14524
  }
14477
- scheduler.frameloop = frameloop;
14525
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14526
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14527
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14478
14528
  onCreated = onCreatedCallback;
14479
14529
  configured = true;
14480
14530
  resolve();
@@ -14516,18 +14566,23 @@ function unmountComponentAtNode(canvas, callback) {
14516
14566
  const fiber = root?.fiber;
14517
14567
  if (fiber) {
14518
14568
  const state = root?.store.getState();
14519
- if (state) state.internal.active = false;
14569
+ if (state) {
14570
+ state.internal.active = false;
14571
+ const unregisterRoot = state.internal.unregisterRoot;
14572
+ if (unregisterRoot) {
14573
+ unregisterRoot();
14574
+ state.internal.unregisterRoot = void 0;
14575
+ }
14576
+ }
14520
14577
  reconciler.updateContainer(null, fiber, null, () => {
14521
14578
  if (state) {
14522
14579
  setTimeout(() => {
14523
14580
  try {
14524
14581
  const renderer = state.internal.actualRenderer;
14525
- const unregisterRoot = state.internal.unregisterRoot;
14526
- if (unregisterRoot) unregisterRoot();
14527
14582
  const unregisterPrimary = state.internal.unregisterPrimary;
14528
14583
  if (unregisterPrimary) unregisterPrimary();
14529
14584
  const canvasTarget = state.internal.canvasTarget;
14530
- if (canvasTarget?.dispose) canvasTarget.dispose();
14585
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14531
14586
  state.events.disconnect?.();
14532
14587
  cleanupHelperGroup(root.store);
14533
14588
  if (state.isLegacy && renderer) {
@@ -14971,9 +15026,9 @@ function CanvasImpl({
14971
15026
  React.useMemo(() => extend(THREE), []);
14972
15027
  const Bridge = useBridge();
14973
15028
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
14974
- const hasInitialSizeRef = React.useRef(false);
15029
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
14975
15030
  const measureConfig = React.useMemo(() => {
14976
- if (!hasInitialSizeRef.current) {
15031
+ if (!hasInitialSize) {
14977
15032
  return {
14978
15033
  ...resize,
14979
15034
  scroll: resize?.scroll ?? true,
@@ -14985,7 +15040,7 @@ function CanvasImpl({
14985
15040
  debounce: { scroll: 50, resize: 0 },
14986
15041
  ...resize
14987
15042
  };
14988
- }, [resize, hasInitialSizeRef.current]);
15043
+ }, [hasInitialSize, resize]);
14989
15044
  const [containerRef, containerRect] = useMeasure(measureConfig);
14990
15045
  const effectiveSize = React.useMemo(() => {
14991
15046
  let w = width ?? containerRect.width;
@@ -15001,9 +15056,7 @@ function CanvasImpl({
15001
15056
  left: containerRect.left
15002
15057
  };
15003
15058
  }, [width, height, containerRect, forceEven]);
15004
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15005
- hasInitialSizeRef.current = true;
15006
- }
15059
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15007
15060
  const canvasRef = React.useRef(null);
15008
15061
  const divRef = React.useRef(null);
15009
15062
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15117,6 +15170,7 @@ function CanvasImpl({
15117
15170
  const canvas = canvasRef.current;
15118
15171
  if (canvas) {
15119
15172
  return () => {
15173
+ if (canvas.isConnected) return;
15120
15174
  unmountComponentAtNode(canvas);
15121
15175
  root.current = null;
15122
15176
  };