@react-three/fiber 10.0.0-canary.8f5b4d3 → 10.0.0-canary.979dc2d

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();
@@ -911,7 +909,7 @@ const createStore = (invalidate, advance) => {
911
909
  size: newSize,
912
910
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
913
911
  }));
914
- getScheduler().invalidate();
912
+ get().invalidate();
915
913
  }
916
914
  }
917
915
  return;
@@ -926,15 +924,16 @@ const createStore = (invalidate, advance) => {
926
924
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
927
925
  _sizeImperative: true
928
926
  }));
929
- getScheduler().invalidate();
927
+ get().invalidate();
930
928
  },
931
929
  setDpr: (dpr) => set((state2) => {
932
930
  const resolved = calculateDpr(dpr);
933
931
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
934
932
  }),
935
933
  setFrameloop: (frameloop = "always") => {
934
+ const rootId = get().internal.rootId;
935
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
936
936
  set(() => ({ frameloop }));
937
- getScheduler().frameloop = frameloop;
938
937
  },
939
938
  setError: (error) => set(() => ({ error })),
940
939
  error: null,
@@ -1050,17 +1049,11 @@ const createStore = (invalidate, advance) => {
1050
1049
  oldSize = size;
1051
1050
  oldDpr = viewport.dpr;
1052
1051
  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
- }
1052
+ const resizeTarget = canvasTarget ?? actualRenderer;
1053
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1054
+ resizeTarget.setSize(size.width, size.height, false);
1055
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1056
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1064
1057
  }
1065
1058
  if (camera !== oldCamera) {
1066
1059
  oldCamera = camera;
@@ -1122,7 +1115,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1122
1115
  const results = cacheKeys.map(
1123
1116
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1124
1117
  );
1125
- return Array.isArray(input) ? results : results[0];
1118
+ const stableRef = useRef(null);
1119
+ const previous = stableRef.current;
1120
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1121
+ stableRef.current = stable;
1122
+ return Array.isArray(input) ? stable : stable[0];
1126
1123
  }
1127
1124
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1128
1125
  const inputs = Array.isArray(input) ? input : [input];
@@ -1242,6 +1239,13 @@ function useFrame(callback, priorityOrOptions) {
1242
1239
  * Use for controlling the entire frame loop, adding phases, etc.
1243
1240
  */
1244
1241
  scheduler: scheduler2,
1242
+ /**
1243
+ * The root that currently owns this job. Resolve on access because ambient
1244
+ * jobs can be adopted by a Canvas after the controls object is created.
1245
+ */
1246
+ get rootId() {
1247
+ return getScheduler().getJobRootId(id);
1248
+ },
1245
1249
  /**
1246
1250
  * Manually step this job only.
1247
1251
  * Bypasses FPS limiting - always runs.
@@ -1258,6 +1262,14 @@ function useFrame(callback, priorityOrOptions) {
1258
1262
  stepAll: (timestamp) => {
1259
1263
  getScheduler().step(timestamp);
1260
1264
  },
1265
+ /**
1266
+ * Request frames for this job's owning root without waking sibling roots.
1267
+ */
1268
+ invalidate: (frames, stackFrames) => {
1269
+ const scheduler3 = getScheduler();
1270
+ const rootId = scheduler3.getJobRootId(id);
1271
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1272
+ },
1261
1273
  /**
1262
1274
  * Pause this job (set enabled=false).
1263
1275
  * Job remains registered but won't run.
@@ -1287,6 +1299,11 @@ function getUrls(input) {
1287
1299
  if (Array.isArray(input)) return input;
1288
1300
  return Object.values(input);
1289
1301
  }
1302
+ function inputSignature(input) {
1303
+ if (typeof input === "string") return input;
1304
+ if (Array.isArray(input)) return input.join("\0");
1305
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1306
+ }
1290
1307
  function allUrlsCached(urls, textureCache) {
1291
1308
  return urls.every((url) => textureCache.has(url));
1292
1309
  }
@@ -1312,18 +1329,19 @@ function useTexture(input, optionsOrOnLoad) {
1312
1329
  const onLoadRef = useRef(onLoad);
1313
1330
  onLoadRef.current = onLoad;
1314
1331
  const onLoadCalledForRef = useRef(null);
1315
- const urls = useMemo(() => getUrls(input), [input]);
1332
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1333
+ const stableInput = useMemo(() => input, [inputKey]);
1334
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1316
1335
  const cachedResult = useMemo(() => {
1317
1336
  if (!cache) return null;
1318
1337
  const textures = store.getState().textures;
1319
1338
  if (!allUrlsCached(urls, textures)) return null;
1320
- return buildFromCache(input, textures);
1321
- }, [cache, urls, input, store]);
1339
+ return buildFromCache(stableInput, textures);
1340
+ }, [cache, urls, stableInput, store]);
1322
1341
  const loadedTextures = useLoader(
1323
1342
  TextureLoader,
1324
- IsObject(input) ? Object.values(input) : input
1343
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1325
1344
  );
1326
- const inputKey = urls.join("\0");
1327
1345
  useLayoutEffect(() => {
1328
1346
  if (cachedResult) return;
1329
1347
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1350,28 +1368,28 @@ function useTexture(input, optionsOrOnLoad) {
1350
1368
  }, [renderer, loadedTextures, cachedResult]);
1351
1369
  const mappedTextures = useMemo(() => {
1352
1370
  if (cachedResult) return cachedResult;
1353
- if (IsObject(input)) {
1371
+ if (IsObject(stableInput)) {
1354
1372
  const keyed = {};
1355
1373
  const textureArray = loadedTextures;
1356
1374
  let i = 0;
1357
- for (const key in input) keyed[key] = textureArray[i++];
1375
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1358
1376
  return keyed;
1359
1377
  } else {
1360
1378
  return loadedTextures;
1361
1379
  }
1362
- }, [input, loadedTextures, cachedResult]);
1380
+ }, [stableInput, loadedTextures, cachedResult]);
1363
1381
  useEffect(() => {
1364
1382
  if (!cache) return;
1365
1383
  const urlTextureMap = [];
1366
- if (typeof input === "string") {
1367
- urlTextureMap.push([input, mappedTextures]);
1368
- } else if (Array.isArray(input)) {
1384
+ if (typeof stableInput === "string") {
1385
+ urlTextureMap.push([stableInput, mappedTextures]);
1386
+ } else if (Array.isArray(stableInput)) {
1369
1387
  const textureArray = mappedTextures;
1370
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1371
- } else if (IsObject(input)) {
1388
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1389
+ } else if (IsObject(stableInput)) {
1372
1390
  const textureRecord = mappedTextures;
1373
- for (const key in input) {
1374
- const url = input[key];
1391
+ for (const key in stableInput) {
1392
+ const url = stableInput[key];
1375
1393
  urlTextureMap.push([url, textureRecord[key]]);
1376
1394
  }
1377
1395
  }
@@ -1400,7 +1418,7 @@ function useTexture(input, optionsOrOnLoad) {
1400
1418
  }
1401
1419
  return { _textureRefs: refs };
1402
1420
  });
1403
- }, [cache, input, mappedTextures, store]);
1421
+ }, [cache, stableInput, mappedTextures, store]);
1404
1422
  return mappedTextures;
1405
1423
  }
1406
1424
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1669,13 +1687,19 @@ function addTail(callback) {
1669
1687
  return getScheduler().onIdle(callback);
1670
1688
  }
1671
1689
  function invalidate(state, frames = 1, stackFrames = false) {
1672
- getScheduler().invalidate(frames, stackFrames);
1690
+ const rootId = state?.internal?.rootId;
1691
+ const scheduler = getScheduler();
1692
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1693
+ else scheduler.invalidate(frames, stackFrames);
1673
1694
  }
1674
- function advance(timestamp) {
1675
- getScheduler().step(timestamp);
1695
+ function advance(timestamp, runGlobalEffects, state) {
1696
+ const rootId = state?.internal?.rootId;
1697
+ const scheduler = getScheduler();
1698
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1699
+ else scheduler.step(timestamp);
1676
1700
  }
1677
1701
 
1678
- const version = "10.0.0-alpha.3";
1702
+ const version = "10.0.0-alpha.4";
1679
1703
  const packageData = {
1680
1704
  version: version};
1681
1705
 
@@ -12958,7 +12982,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12958
12982
  const captureData = pointerState.captured.get(obj);
12959
12983
  if (captureData) {
12960
12984
  pointerState.captured.delete(obj);
12961
- captureData.target.releasePointerCapture(pointerId);
12985
+ try {
12986
+ captureData.target.releasePointerCapture?.(pointerId);
12987
+ } catch {
12988
+ }
12962
12989
  }
12963
12990
  }
12964
12991
  function swapInteractivity(store, object, newObject) {
@@ -13052,7 +13079,9 @@ function createEvents(store) {
13052
13079
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13053
13080
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13054
13081
  }
13055
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13082
+ if (!state2.raycaster.camera) return [];
13083
+ obj.updateWorldMatrix(true, true);
13084
+ return state2.raycaster.intersectObject(obj, true);
13056
13085
  }
13057
13086
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13058
13087
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14282,7 +14311,7 @@ function createRoot(canvas) {
14282
14311
  const handleXRFrame = (timestamp, _frame) => {
14283
14312
  const state2 = store.getState();
14284
14313
  if (state2.frameloop === "never") return;
14285
- advance(timestamp);
14314
+ advance(timestamp, true, state2);
14286
14315
  };
14287
14316
  const actualRenderer = state.internal.actualRenderer;
14288
14317
  const handleSessionChange = () => {
@@ -14290,7 +14319,7 @@ function createRoot(canvas) {
14290
14319
  const renderer2 = state2.internal.actualRenderer;
14291
14320
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14292
14321
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14293
- if (!renderer2.xr.isPresenting) invalidate();
14322
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14294
14323
  };
14295
14324
  const xr = {
14296
14325
  connect() {
@@ -14372,14 +14401,23 @@ function createRoot(canvas) {
14372
14401
  const newRootId = canvasId || scheduler.generateRootId();
14373
14402
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14374
14403
  getState: () => store.getState(),
14375
- onError: (err) => store.getState().setError(err)
14404
+ onError: (err) => store.getState().setError(err),
14405
+ frameloop: store.getState().frameloop,
14406
+ before: schedulerConfig?.before,
14407
+ after: schedulerConfig?.after,
14408
+ order: schedulerConfig?.order
14376
14409
  });
14377
14410
  const unregisterCanvasTarget = scheduler.register(
14378
14411
  () => {
14379
14412
  const state2 = store.getState();
14380
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14413
+ const canvasTarget = state2.internal.canvasTarget;
14414
+ if (canvasTarget) {
14381
14415
  const renderer2 = state2.internal.actualRenderer;
14382
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14416
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14417
+ if (state2.internal.canvasTargetSizeDirty) {
14418
+ state2.internal.canvasTargetSizeDirty = false;
14419
+ renderer2.backend?.updateSize?.();
14420
+ }
14383
14421
  }
14384
14422
  },
14385
14423
  {
@@ -14448,8 +14486,7 @@ function createRoot(canvas) {
14448
14486
  phase: "render",
14449
14487
  system: true,
14450
14488
  // Internal flag: this is a system job, not user-controlled
14451
- // Apply scheduler config for render ordering and rate limiting
14452
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14489
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14453
14490
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14454
14491
  }
14455
14492
  );
@@ -14468,8 +14505,21 @@ function createRoot(canvas) {
14468
14505
  scheduler
14469
14506
  }
14470
14507
  }));
14508
+ } else {
14509
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14510
+ if (constraintsChanged) {
14511
+ scheduler.setRootConstraints(rootId, {
14512
+ before: schedulerConfig?.before,
14513
+ after: schedulerConfig?.after
14514
+ });
14515
+ }
14516
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14517
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14518
+ }
14471
14519
  }
14472
- scheduler.frameloop = frameloop;
14520
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14521
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14522
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14473
14523
  onCreated = onCreatedCallback;
14474
14524
  configured = true;
14475
14525
  resolve();
@@ -14511,18 +14561,23 @@ function unmountComponentAtNode(canvas, callback) {
14511
14561
  const fiber = root?.fiber;
14512
14562
  if (fiber) {
14513
14563
  const state = root?.store.getState();
14514
- if (state) state.internal.active = false;
14564
+ if (state) {
14565
+ state.internal.active = false;
14566
+ const unregisterRoot = state.internal.unregisterRoot;
14567
+ if (unregisterRoot) {
14568
+ unregisterRoot();
14569
+ state.internal.unregisterRoot = void 0;
14570
+ }
14571
+ }
14515
14572
  reconciler.updateContainer(null, fiber, null, () => {
14516
14573
  if (state) {
14517
14574
  setTimeout(() => {
14518
14575
  try {
14519
14576
  const renderer = state.internal.actualRenderer;
14520
- const unregisterRoot = state.internal.unregisterRoot;
14521
- if (unregisterRoot) unregisterRoot();
14522
14577
  const unregisterPrimary = state.internal.unregisterPrimary;
14523
14578
  if (unregisterPrimary) unregisterPrimary();
14524
14579
  const canvasTarget = state.internal.canvasTarget;
14525
- if (canvasTarget?.dispose) canvasTarget.dispose();
14580
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14526
14581
  state.events.disconnect?.();
14527
14582
  cleanupHelperGroup(root.store);
14528
14583
  if (state.isLegacy && renderer) {
@@ -14966,9 +15021,9 @@ function CanvasImpl({
14966
15021
  React.useMemo(() => extend(THREE), []);
14967
15022
  const Bridge = useBridge();
14968
15023
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
14969
- const hasInitialSizeRef = React.useRef(false);
15024
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
14970
15025
  const measureConfig = React.useMemo(() => {
14971
- if (!hasInitialSizeRef.current) {
15026
+ if (!hasInitialSize) {
14972
15027
  return {
14973
15028
  ...resize,
14974
15029
  scroll: resize?.scroll ?? true,
@@ -14980,7 +15035,7 @@ function CanvasImpl({
14980
15035
  debounce: { scroll: 50, resize: 0 },
14981
15036
  ...resize
14982
15037
  };
14983
- }, [resize, hasInitialSizeRef.current]);
15038
+ }, [hasInitialSize, resize]);
14984
15039
  const [containerRef, containerRect] = useMeasure(measureConfig);
14985
15040
  const effectiveSize = React.useMemo(() => {
14986
15041
  let w = width ?? containerRect.width;
@@ -14996,9 +15051,7 @@ function CanvasImpl({
14996
15051
  left: containerRect.left
14997
15052
  };
14998
15053
  }, [width, height, containerRect, forceEven]);
14999
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15000
- hasInitialSizeRef.current = true;
15001
- }
15054
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15002
15055
  const canvasRef = React.useRef(null);
15003
15056
  const divRef = React.useRef(null);
15004
15057
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15112,6 +15165,7 @@ function CanvasImpl({
15112
15165
  const canvas = canvasRef.current;
15113
15166
  if (canvas) {
15114
15167
  return () => {
15168
+ if (canvas.isConnected) return;
15115
15169
  unmountComponentAtNode(canvas);
15116
15170
  root.current = null;
15117
15171
  };