@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/index.cjs CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
4
  const three = require('three');
5
- const Inspector_js = require('three/addons/inspector/Inspector.js');
6
5
  const jsxRuntime = require('react/jsx-runtime');
7
6
  const React = require('react');
8
7
  const useMeasure = require('react-use-measure');
@@ -52,18 +51,22 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
52
51
 
53
52
  const R3F_BUILD_LEGACY = true;
54
53
  const R3F_BUILD_WEBGPU = true;
54
+ async function loadInspector() {
55
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
56
+ return Inspector;
57
+ }
55
58
 
56
59
  const THREE = /*#__PURE__*/_mergeNamespaces({
57
60
  __proto__: null,
58
61
  CubeRenderTarget: webgpu.CubeRenderTarget,
59
62
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
60
- Inspector: Inspector_js.Inspector,
61
63
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
62
64
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
63
65
  RenderTargetCompat: webgpu.RenderTarget,
64
66
  WebGLCubeRenderTarget: three.WebGLCubeRenderTarget,
65
67
  WebGLRenderTarget: three.WebGLRenderTarget,
66
- WebGLRenderer: three.WebGLRenderer
68
+ WebGLRenderer: three.WebGLRenderer,
69
+ loadInspector: loadInspector
67
70
  }, [webgpu__namespace]);
68
71
 
69
72
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -922,7 +925,7 @@ const createStore = (invalidate, advance) => {
922
925
  size: newSize,
923
926
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
924
927
  }));
925
- scheduler.getScheduler().invalidate();
928
+ get().invalidate();
926
929
  }
927
930
  }
928
931
  return;
@@ -937,15 +940,16 @@ const createStore = (invalidate, advance) => {
937
940
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
938
941
  _sizeImperative: true
939
942
  }));
940
- scheduler.getScheduler().invalidate();
943
+ get().invalidate();
941
944
  },
942
945
  setDpr: (dpr) => set((state2) => {
943
946
  const resolved = calculateDpr(dpr);
944
947
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
945
948
  }),
946
949
  setFrameloop: (frameloop = "always") => {
950
+ const rootId = get().internal.rootId;
951
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
947
952
  set(() => ({ frameloop }));
948
- scheduler.getScheduler().frameloop = frameloop;
949
953
  },
950
954
  setError: (error) => set(() => ({ error })),
951
955
  error: null,
@@ -1061,17 +1065,11 @@ const createStore = (invalidate, advance) => {
1061
1065
  oldSize = size;
1062
1066
  oldDpr = viewport.dpr;
1063
1067
  updateCamera(camera, size);
1064
- if (internal.isSecondary && canvasTarget) {
1065
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1066
- canvasTarget.setSize(size.width, size.height, false);
1067
- } else {
1068
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1069
- actualRenderer.setSize(size.width, size.height, false);
1070
- if (canvasTarget) {
1071
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1072
- canvasTarget.setSize(size.width, size.height, false);
1073
- }
1074
- }
1068
+ const resizeTarget = canvasTarget ?? actualRenderer;
1069
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1070
+ resizeTarget.setSize(size.width, size.height, false);
1071
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1072
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1075
1073
  }
1076
1074
  if (camera !== oldCamera) {
1077
1075
  oldCamera = camera;
@@ -1133,7 +1131,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1133
1131
  const results = cacheKeys.map(
1134
1132
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1135
1133
  );
1136
- return Array.isArray(input) ? results : results[0];
1134
+ const stableRef = React.useRef(null);
1135
+ const previous = stableRef.current;
1136
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1137
+ stableRef.current = stable;
1138
+ return Array.isArray(input) ? stable : stable[0];
1137
1139
  }
1138
1140
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1139
1141
  const inputs = Array.isArray(input) ? input : [input];
@@ -1253,6 +1255,13 @@ function useFrame(callback, priorityOrOptions) {
1253
1255
  * Use for controlling the entire frame loop, adding phases, etc.
1254
1256
  */
1255
1257
  scheduler: scheduler2,
1258
+ /**
1259
+ * The root that currently owns this job. Resolve on access because ambient
1260
+ * jobs can be adopted by a Canvas after the controls object is created.
1261
+ */
1262
+ get rootId() {
1263
+ return scheduler.getScheduler().getJobRootId(id);
1264
+ },
1256
1265
  /**
1257
1266
  * Manually step this job only.
1258
1267
  * Bypasses FPS limiting - always runs.
@@ -1269,6 +1278,14 @@ function useFrame(callback, priorityOrOptions) {
1269
1278
  stepAll: (timestamp) => {
1270
1279
  scheduler.getScheduler().step(timestamp);
1271
1280
  },
1281
+ /**
1282
+ * Request frames for this job's owning root without waking sibling roots.
1283
+ */
1284
+ invalidate: (frames, stackFrames) => {
1285
+ const scheduler3 = scheduler.getScheduler();
1286
+ const rootId = scheduler3.getJobRootId(id);
1287
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1288
+ },
1272
1289
  /**
1273
1290
  * Pause this job (set enabled=false).
1274
1291
  * Job remains registered but won't run.
@@ -1298,6 +1315,11 @@ function getUrls(input) {
1298
1315
  if (Array.isArray(input)) return input;
1299
1316
  return Object.values(input);
1300
1317
  }
1318
+ function inputSignature(input) {
1319
+ if (typeof input === "string") return input;
1320
+ if (Array.isArray(input)) return input.join("\0");
1321
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1322
+ }
1301
1323
  function allUrlsCached(urls, textureCache) {
1302
1324
  return urls.every((url) => textureCache.has(url));
1303
1325
  }
@@ -1323,18 +1345,19 @@ function useTexture(input, optionsOrOnLoad) {
1323
1345
  const onLoadRef = React.useRef(onLoad);
1324
1346
  onLoadRef.current = onLoad;
1325
1347
  const onLoadCalledForRef = React.useRef(null);
1326
- const urls = React.useMemo(() => getUrls(input), [input]);
1348
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1349
+ const stableInput = React.useMemo(() => input, [inputKey]);
1350
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1327
1351
  const cachedResult = React.useMemo(() => {
1328
1352
  if (!cache) return null;
1329
1353
  const textures = store.getState().textures;
1330
1354
  if (!allUrlsCached(urls, textures)) return null;
1331
- return buildFromCache(input, textures);
1332
- }, [cache, urls, input, store]);
1355
+ return buildFromCache(stableInput, textures);
1356
+ }, [cache, urls, stableInput, store]);
1333
1357
  const loadedTextures = useLoader(
1334
1358
  webgpu.TextureLoader,
1335
- IsObject(input) ? Object.values(input) : input
1359
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1336
1360
  );
1337
- const inputKey = urls.join("\0");
1338
1361
  React.useLayoutEffect(() => {
1339
1362
  if (cachedResult) return;
1340
1363
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1361,28 +1384,28 @@ function useTexture(input, optionsOrOnLoad) {
1361
1384
  }, [renderer, loadedTextures, cachedResult]);
1362
1385
  const mappedTextures = React.useMemo(() => {
1363
1386
  if (cachedResult) return cachedResult;
1364
- if (IsObject(input)) {
1387
+ if (IsObject(stableInput)) {
1365
1388
  const keyed = {};
1366
1389
  const textureArray = loadedTextures;
1367
1390
  let i = 0;
1368
- for (const key in input) keyed[key] = textureArray[i++];
1391
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1369
1392
  return keyed;
1370
1393
  } else {
1371
1394
  return loadedTextures;
1372
1395
  }
1373
- }, [input, loadedTextures, cachedResult]);
1396
+ }, [stableInput, loadedTextures, cachedResult]);
1374
1397
  React.useEffect(() => {
1375
1398
  if (!cache) return;
1376
1399
  const urlTextureMap = [];
1377
- if (typeof input === "string") {
1378
- urlTextureMap.push([input, mappedTextures]);
1379
- } else if (Array.isArray(input)) {
1400
+ if (typeof stableInput === "string") {
1401
+ urlTextureMap.push([stableInput, mappedTextures]);
1402
+ } else if (Array.isArray(stableInput)) {
1380
1403
  const textureArray = mappedTextures;
1381
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1382
- } else if (IsObject(input)) {
1404
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1405
+ } else if (IsObject(stableInput)) {
1383
1406
  const textureRecord = mappedTextures;
1384
- for (const key in input) {
1385
- const url = input[key];
1407
+ for (const key in stableInput) {
1408
+ const url = stableInput[key];
1386
1409
  urlTextureMap.push([url, textureRecord[key]]);
1387
1410
  }
1388
1411
  }
@@ -1411,7 +1434,7 @@ function useTexture(input, optionsOrOnLoad) {
1411
1434
  }
1412
1435
  return { _textureRefs: refs };
1413
1436
  });
1414
- }, [cache, input, mappedTextures, store]);
1437
+ }, [cache, stableInput, mappedTextures, store]);
1415
1438
  return mappedTextures;
1416
1439
  }
1417
1440
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1682,13 +1705,19 @@ function addTail(callback) {
1682
1705
  return scheduler.getScheduler().onIdle(callback);
1683
1706
  }
1684
1707
  function invalidate(state, frames = 1, stackFrames = false) {
1685
- scheduler.getScheduler().invalidate(frames, stackFrames);
1708
+ const rootId = state?.internal?.rootId;
1709
+ const scheduler$1 = scheduler.getScheduler();
1710
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1711
+ else scheduler$1.invalidate(frames, stackFrames);
1686
1712
  }
1687
- function advance(timestamp) {
1688
- scheduler.getScheduler().step(timestamp);
1713
+ function advance(timestamp, runGlobalEffects, state) {
1714
+ const rootId = state?.internal?.rootId;
1715
+ const scheduler$1 = scheduler.getScheduler();
1716
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1717
+ else scheduler$1.step(timestamp);
1689
1718
  }
1690
1719
 
1691
- const version = "10.0.0-alpha.3";
1720
+ const version = "10.0.0-alpha.4";
1692
1721
  const packageData = {
1693
1722
  version: version};
1694
1723
 
@@ -12971,7 +13000,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12971
13000
  const captureData = pointerState.captured.get(obj);
12972
13001
  if (captureData) {
12973
13002
  pointerState.captured.delete(obj);
12974
- captureData.target.releasePointerCapture(pointerId);
13003
+ try {
13004
+ captureData.target.releasePointerCapture?.(pointerId);
13005
+ } catch {
13006
+ }
12975
13007
  }
12976
13008
  }
12977
13009
  function swapInteractivity(store, object, newObject) {
@@ -13065,7 +13097,9 @@ function createEvents(store) {
13065
13097
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13066
13098
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13067
13099
  }
13068
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13100
+ if (!state2.raycaster.camera) return [];
13101
+ obj.updateWorldMatrix(true, true);
13102
+ return state2.raycaster.intersectObject(obj, true);
13069
13103
  }
13070
13104
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13071
13105
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14217,9 +14251,8 @@ function createRoot(canvas) {
14217
14251
  if (!renderer.hasInitialized?.()) {
14218
14252
  const size2 = computeInitialSize(canvas, propsSize);
14219
14253
  if (size2.width > 0 && size2.height > 0) {
14220
- const pixelRatio = calculateDpr(dpr);
14221
- canvas.width = size2.width * pixelRatio;
14222
- canvas.height = size2.height * pixelRatio;
14254
+ renderer.setPixelRatio(calculateDpr(dpr));
14255
+ renderer.setSize(size2.width, size2.height, false);
14223
14256
  }
14224
14257
  await renderer.init();
14225
14258
  }
@@ -14228,7 +14261,7 @@ function createRoot(canvas) {
14228
14261
  state.internal.actualRenderer = renderer;
14229
14262
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14230
14263
  if (canvasId && !state.internal.isSecondary) {
14231
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14264
+ const canvasTarget = renderer.getCanvasTarget?.();
14232
14265
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14233
14266
  state.set((prev) => ({
14234
14267
  internal: {
@@ -14342,7 +14375,7 @@ function createRoot(canvas) {
14342
14375
  const handleXRFrame = (timestamp, _frame) => {
14343
14376
  const state2 = store.getState();
14344
14377
  if (state2.frameloop === "never") return;
14345
- advance(timestamp);
14378
+ advance(timestamp, true, state2);
14346
14379
  };
14347
14380
  const actualRenderer = state.internal.actualRenderer;
14348
14381
  const handleSessionChange = () => {
@@ -14350,7 +14383,7 @@ function createRoot(canvas) {
14350
14383
  const renderer2 = state2.internal.actualRenderer;
14351
14384
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14352
14385
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14353
- if (!renderer2.xr.isPresenting) invalidate();
14386
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14354
14387
  };
14355
14388
  const xr = {
14356
14389
  connect() {
@@ -14432,14 +14465,23 @@ function createRoot(canvas) {
14432
14465
  const newRootId = canvasId || scheduler$1.generateRootId();
14433
14466
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14434
14467
  getState: () => store.getState(),
14435
- onError: (err) => store.getState().setError(err)
14468
+ onError: (err) => store.getState().setError(err),
14469
+ frameloop: store.getState().frameloop,
14470
+ before: schedulerConfig?.before,
14471
+ after: schedulerConfig?.after,
14472
+ order: schedulerConfig?.order
14436
14473
  });
14437
14474
  const unregisterCanvasTarget = scheduler$1.register(
14438
14475
  () => {
14439
14476
  const state2 = store.getState();
14440
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14477
+ const canvasTarget = state2.internal.canvasTarget;
14478
+ if (canvasTarget) {
14441
14479
  const renderer2 = state2.internal.actualRenderer;
14442
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14480
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14481
+ if (state2.internal.canvasTargetSizeDirty) {
14482
+ state2.internal.canvasTargetSizeDirty = false;
14483
+ renderer2.backend?.updateSize?.();
14484
+ }
14443
14485
  }
14444
14486
  },
14445
14487
  {
@@ -14508,8 +14550,7 @@ function createRoot(canvas) {
14508
14550
  phase: "render",
14509
14551
  system: true,
14510
14552
  // Internal flag: this is a system job, not user-controlled
14511
- // Apply scheduler config for render ordering and rate limiting
14512
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14553
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14513
14554
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14514
14555
  }
14515
14556
  );
@@ -14528,8 +14569,21 @@ function createRoot(canvas) {
14528
14569
  scheduler: scheduler$1
14529
14570
  }
14530
14571
  }));
14572
+ } else {
14573
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14574
+ if (constraintsChanged) {
14575
+ scheduler$1.setRootConstraints(rootId, {
14576
+ before: schedulerConfig?.before,
14577
+ after: schedulerConfig?.after
14578
+ });
14579
+ }
14580
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14581
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14582
+ }
14531
14583
  }
14532
- scheduler$1.frameloop = frameloop;
14584
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14585
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14586
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14533
14587
  onCreated = onCreatedCallback;
14534
14588
  configured = true;
14535
14589
  resolve();
@@ -14571,18 +14625,23 @@ function unmountComponentAtNode(canvas, callback) {
14571
14625
  const fiber = root?.fiber;
14572
14626
  if (fiber) {
14573
14627
  const state = root?.store.getState();
14574
- if (state) state.internal.active = false;
14628
+ if (state) {
14629
+ state.internal.active = false;
14630
+ const unregisterRoot = state.internal.unregisterRoot;
14631
+ if (unregisterRoot) {
14632
+ unregisterRoot();
14633
+ state.internal.unregisterRoot = void 0;
14634
+ }
14635
+ }
14575
14636
  reconciler.updateContainer(null, fiber, null, () => {
14576
14637
  if (state) {
14577
14638
  setTimeout(() => {
14578
14639
  try {
14579
14640
  const renderer = state.internal.actualRenderer;
14580
- const unregisterRoot = state.internal.unregisterRoot;
14581
- if (unregisterRoot) unregisterRoot();
14582
14641
  const unregisterPrimary = state.internal.unregisterPrimary;
14583
14642
  if (unregisterPrimary) unregisterPrimary();
14584
14643
  const canvasTarget = state.internal.canvasTarget;
14585
- if (canvasTarget?.dispose) canvasTarget.dispose();
14644
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14586
14645
  state.events.disconnect?.();
14587
14646
  cleanupHelperGroup(root.store);
14588
14647
  if (state.isLegacy && renderer) {
@@ -15026,9 +15085,9 @@ function CanvasImpl({
15026
15085
  React__namespace.useMemo(() => extend(THREE), []);
15027
15086
  const Bridge = useBridge();
15028
15087
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15029
- const hasInitialSizeRef = React__namespace.useRef(false);
15088
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15030
15089
  const measureConfig = React__namespace.useMemo(() => {
15031
- if (!hasInitialSizeRef.current) {
15090
+ if (!hasInitialSize) {
15032
15091
  return {
15033
15092
  ...resize,
15034
15093
  scroll: resize?.scroll ?? true,
@@ -15040,7 +15099,7 @@ function CanvasImpl({
15040
15099
  debounce: { scroll: 50, resize: 0 },
15041
15100
  ...resize
15042
15101
  };
15043
- }, [resize, hasInitialSizeRef.current]);
15102
+ }, [hasInitialSize, resize]);
15044
15103
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15045
15104
  const effectiveSize = React__namespace.useMemo(() => {
15046
15105
  let w = width ?? containerRect.width;
@@ -15056,9 +15115,7 @@ function CanvasImpl({
15056
15115
  left: containerRect.left
15057
15116
  };
15058
15117
  }, [width, height, containerRect, forceEven]);
15059
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15060
- hasInitialSizeRef.current = true;
15061
- }
15118
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15062
15119
  const canvasRef = React__namespace.useRef(null);
15063
15120
  const divRef = React__namespace.useRef(null);
15064
15121
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15172,6 +15229,7 @@ function CanvasImpl({
15172
15229
  const canvas = canvasRef.current;
15173
15230
  if (canvas) {
15174
15231
  return () => {
15232
+ if (canvas.isConnected) return;
15175
15233
  unmountComponentAtNode(canvas);
15176
15234
  root.current = null;
15177
15235
  };