@react-three/fiber 10.0.0-canary.4bc4a97 → 10.0.0-canary.4f316e6

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();
@@ -619,7 +622,12 @@ function diffProps(instance, newProps) {
619
622
  const changedProps = {};
620
623
  for (const prop in newProps) {
621
624
  if (RESERVED_PROPS.includes(prop)) continue;
622
- if (is.equ(newProps[prop], instance.props[prop])) continue;
625
+ if (is.equ(newProps[prop], instance.props[prop])) {
626
+ const value = newProps[prop];
627
+ if (!isCopyable(value)) continue;
628
+ const { target } = resolve(instance.object, prop);
629
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
630
+ }
623
631
  changedProps[prop] = newProps[prop];
624
632
  for (const other in newProps) {
625
633
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -922,7 +930,7 @@ const createStore = (invalidate, advance) => {
922
930
  size: newSize,
923
931
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
924
932
  }));
925
- scheduler.getScheduler().invalidate();
933
+ get().invalidate();
926
934
  }
927
935
  }
928
936
  return;
@@ -937,15 +945,16 @@ const createStore = (invalidate, advance) => {
937
945
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
938
946
  _sizeImperative: true
939
947
  }));
940
- scheduler.getScheduler().invalidate();
948
+ get().invalidate();
941
949
  },
942
950
  setDpr: (dpr) => set((state2) => {
943
951
  const resolved = calculateDpr(dpr);
944
952
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
945
953
  }),
946
954
  setFrameloop: (frameloop = "always") => {
955
+ const rootId = get().internal.rootId;
956
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
947
957
  set(() => ({ frameloop }));
948
- scheduler.getScheduler().frameloop = frameloop;
949
958
  },
950
959
  setError: (error) => set(() => ({ error })),
951
960
  error: null,
@@ -1061,17 +1070,11 @@ const createStore = (invalidate, advance) => {
1061
1070
  oldSize = size;
1062
1071
  oldDpr = viewport.dpr;
1063
1072
  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
- }
1073
+ const resizeTarget = canvasTarget ?? actualRenderer;
1074
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1075
+ resizeTarget.setSize(size.width, size.height, false);
1076
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1077
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1075
1078
  }
1076
1079
  if (camera !== oldCamera) {
1077
1080
  oldCamera = camera;
@@ -1133,7 +1136,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1133
1136
  const results = cacheKeys.map(
1134
1137
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1135
1138
  );
1136
- return Array.isArray(input) ? results : results[0];
1139
+ const stableRef = React.useRef(null);
1140
+ const previous = stableRef.current;
1141
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1142
+ stableRef.current = stable;
1143
+ return Array.isArray(input) ? stable : stable[0];
1137
1144
  }
1138
1145
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1139
1146
  const inputs = Array.isArray(input) ? input : [input];
@@ -1253,6 +1260,13 @@ function useFrame(callback, priorityOrOptions) {
1253
1260
  * Use for controlling the entire frame loop, adding phases, etc.
1254
1261
  */
1255
1262
  scheduler: scheduler2,
1263
+ /**
1264
+ * The root that currently owns this job. Resolve on access because ambient
1265
+ * jobs can be adopted by a Canvas after the controls object is created.
1266
+ */
1267
+ get rootId() {
1268
+ return scheduler.getScheduler().getJobRootId(id);
1269
+ },
1256
1270
  /**
1257
1271
  * Manually step this job only.
1258
1272
  * Bypasses FPS limiting - always runs.
@@ -1269,6 +1283,14 @@ function useFrame(callback, priorityOrOptions) {
1269
1283
  stepAll: (timestamp) => {
1270
1284
  scheduler.getScheduler().step(timestamp);
1271
1285
  },
1286
+ /**
1287
+ * Request frames for this job's owning root without waking sibling roots.
1288
+ */
1289
+ invalidate: (frames, stackFrames) => {
1290
+ const scheduler3 = scheduler.getScheduler();
1291
+ const rootId = scheduler3.getJobRootId(id);
1292
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1293
+ },
1272
1294
  /**
1273
1295
  * Pause this job (set enabled=false).
1274
1296
  * Job remains registered but won't run.
@@ -1298,6 +1320,11 @@ function getUrls(input) {
1298
1320
  if (Array.isArray(input)) return input;
1299
1321
  return Object.values(input);
1300
1322
  }
1323
+ function inputSignature(input) {
1324
+ if (typeof input === "string") return input;
1325
+ if (Array.isArray(input)) return input.join("\0");
1326
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1327
+ }
1301
1328
  function allUrlsCached(urls, textureCache) {
1302
1329
  return urls.every((url) => textureCache.has(url));
1303
1330
  }
@@ -1323,18 +1350,19 @@ function useTexture(input, optionsOrOnLoad) {
1323
1350
  const onLoadRef = React.useRef(onLoad);
1324
1351
  onLoadRef.current = onLoad;
1325
1352
  const onLoadCalledForRef = React.useRef(null);
1326
- const urls = React.useMemo(() => getUrls(input), [input]);
1353
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1354
+ const stableInput = React.useMemo(() => input, [inputKey]);
1355
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1327
1356
  const cachedResult = React.useMemo(() => {
1328
1357
  if (!cache) return null;
1329
1358
  const textures = store.getState().textures;
1330
1359
  if (!allUrlsCached(urls, textures)) return null;
1331
- return buildFromCache(input, textures);
1332
- }, [cache, urls, input, store]);
1360
+ return buildFromCache(stableInput, textures);
1361
+ }, [cache, urls, stableInput, store]);
1333
1362
  const loadedTextures = useLoader(
1334
1363
  webgpu.TextureLoader,
1335
- IsObject(input) ? Object.values(input) : input
1364
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1336
1365
  );
1337
- const inputKey = urls.join("\0");
1338
1366
  React.useLayoutEffect(() => {
1339
1367
  if (cachedResult) return;
1340
1368
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1361,28 +1389,28 @@ function useTexture(input, optionsOrOnLoad) {
1361
1389
  }, [renderer, loadedTextures, cachedResult]);
1362
1390
  const mappedTextures = React.useMemo(() => {
1363
1391
  if (cachedResult) return cachedResult;
1364
- if (IsObject(input)) {
1392
+ if (IsObject(stableInput)) {
1365
1393
  const keyed = {};
1366
1394
  const textureArray = loadedTextures;
1367
1395
  let i = 0;
1368
- for (const key in input) keyed[key] = textureArray[i++];
1396
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1369
1397
  return keyed;
1370
1398
  } else {
1371
1399
  return loadedTextures;
1372
1400
  }
1373
- }, [input, loadedTextures, cachedResult]);
1401
+ }, [stableInput, loadedTextures, cachedResult]);
1374
1402
  React.useEffect(() => {
1375
1403
  if (!cache) return;
1376
1404
  const urlTextureMap = [];
1377
- if (typeof input === "string") {
1378
- urlTextureMap.push([input, mappedTextures]);
1379
- } else if (Array.isArray(input)) {
1405
+ if (typeof stableInput === "string") {
1406
+ urlTextureMap.push([stableInput, mappedTextures]);
1407
+ } else if (Array.isArray(stableInput)) {
1380
1408
  const textureArray = mappedTextures;
1381
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1382
- } else if (IsObject(input)) {
1409
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1410
+ } else if (IsObject(stableInput)) {
1383
1411
  const textureRecord = mappedTextures;
1384
- for (const key in input) {
1385
- const url = input[key];
1412
+ for (const key in stableInput) {
1413
+ const url = stableInput[key];
1386
1414
  urlTextureMap.push([url, textureRecord[key]]);
1387
1415
  }
1388
1416
  }
@@ -1411,7 +1439,7 @@ function useTexture(input, optionsOrOnLoad) {
1411
1439
  }
1412
1440
  return { _textureRefs: refs };
1413
1441
  });
1414
- }, [cache, input, mappedTextures, store]);
1442
+ }, [cache, stableInput, mappedTextures, store]);
1415
1443
  return mappedTextures;
1416
1444
  }
1417
1445
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1682,13 +1710,19 @@ function addTail(callback) {
1682
1710
  return scheduler.getScheduler().onIdle(callback);
1683
1711
  }
1684
1712
  function invalidate(state, frames = 1, stackFrames = false) {
1685
- scheduler.getScheduler().invalidate(frames, stackFrames);
1713
+ const rootId = state?.internal?.rootId;
1714
+ const scheduler$1 = scheduler.getScheduler();
1715
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1716
+ else scheduler$1.invalidate(frames, stackFrames);
1686
1717
  }
1687
- function advance(timestamp) {
1688
- scheduler.getScheduler().step(timestamp);
1718
+ function advance(timestamp, runGlobalEffects, state) {
1719
+ const rootId = state?.internal?.rootId;
1720
+ const scheduler$1 = scheduler.getScheduler();
1721
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1722
+ else scheduler$1.step(timestamp);
1689
1723
  }
1690
1724
 
1691
- const version = "10.0.0-alpha.3";
1725
+ const version = "10.0.0-alpha.4";
1692
1726
  const packageData = {
1693
1727
  version: version};
1694
1728
 
@@ -14222,9 +14256,8 @@ function createRoot(canvas) {
14222
14256
  if (!renderer.hasInitialized?.()) {
14223
14257
  const size2 = computeInitialSize(canvas, propsSize);
14224
14258
  if (size2.width > 0 && size2.height > 0) {
14225
- const pixelRatio = calculateDpr(dpr);
14226
- canvas.width = size2.width * pixelRatio;
14227
- canvas.height = size2.height * pixelRatio;
14259
+ renderer.setPixelRatio(calculateDpr(dpr));
14260
+ renderer.setSize(size2.width, size2.height, false);
14228
14261
  }
14229
14262
  await renderer.init();
14230
14263
  }
@@ -14233,7 +14266,7 @@ function createRoot(canvas) {
14233
14266
  state.internal.actualRenderer = renderer;
14234
14267
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14235
14268
  if (canvasId && !state.internal.isSecondary) {
14236
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14269
+ const canvasTarget = renderer.getCanvasTarget?.();
14237
14270
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14238
14271
  state.set((prev) => ({
14239
14272
  internal: {
@@ -14347,7 +14380,7 @@ function createRoot(canvas) {
14347
14380
  const handleXRFrame = (timestamp, _frame) => {
14348
14381
  const state2 = store.getState();
14349
14382
  if (state2.frameloop === "never") return;
14350
- advance(timestamp);
14383
+ advance(timestamp, true, state2);
14351
14384
  };
14352
14385
  const actualRenderer = state.internal.actualRenderer;
14353
14386
  const handleSessionChange = () => {
@@ -14355,7 +14388,7 @@ function createRoot(canvas) {
14355
14388
  const renderer2 = state2.internal.actualRenderer;
14356
14389
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14357
14390
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14358
- if (!renderer2.xr.isPresenting) invalidate();
14391
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14359
14392
  };
14360
14393
  const xr = {
14361
14394
  connect() {
@@ -14437,14 +14470,23 @@ function createRoot(canvas) {
14437
14470
  const newRootId = canvasId || scheduler$1.generateRootId();
14438
14471
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14439
14472
  getState: () => store.getState(),
14440
- onError: (err) => store.getState().setError(err)
14473
+ onError: (err) => store.getState().setError(err),
14474
+ frameloop: store.getState().frameloop,
14475
+ before: schedulerConfig?.before,
14476
+ after: schedulerConfig?.after,
14477
+ order: schedulerConfig?.order
14441
14478
  });
14442
14479
  const unregisterCanvasTarget = scheduler$1.register(
14443
14480
  () => {
14444
14481
  const state2 = store.getState();
14445
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14482
+ const canvasTarget = state2.internal.canvasTarget;
14483
+ if (canvasTarget) {
14446
14484
  const renderer2 = state2.internal.actualRenderer;
14447
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14485
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14486
+ if (state2.internal.canvasTargetSizeDirty) {
14487
+ state2.internal.canvasTargetSizeDirty = false;
14488
+ renderer2.backend?.updateSize?.();
14489
+ }
14448
14490
  }
14449
14491
  },
14450
14492
  {
@@ -14513,8 +14555,7 @@ function createRoot(canvas) {
14513
14555
  phase: "render",
14514
14556
  system: true,
14515
14557
  // Internal flag: this is a system job, not user-controlled
14516
- // Apply scheduler config for render ordering and rate limiting
14517
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14558
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14518
14559
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14519
14560
  }
14520
14561
  );
@@ -14533,8 +14574,21 @@ function createRoot(canvas) {
14533
14574
  scheduler: scheduler$1
14534
14575
  }
14535
14576
  }));
14577
+ } else {
14578
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14579
+ if (constraintsChanged) {
14580
+ scheduler$1.setRootConstraints(rootId, {
14581
+ before: schedulerConfig?.before,
14582
+ after: schedulerConfig?.after
14583
+ });
14584
+ }
14585
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14586
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14587
+ }
14536
14588
  }
14537
- scheduler$1.frameloop = frameloop;
14589
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14590
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14591
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14538
14592
  onCreated = onCreatedCallback;
14539
14593
  configured = true;
14540
14594
  resolve();
@@ -14576,18 +14630,23 @@ function unmountComponentAtNode(canvas, callback) {
14576
14630
  const fiber = root?.fiber;
14577
14631
  if (fiber) {
14578
14632
  const state = root?.store.getState();
14579
- if (state) state.internal.active = false;
14633
+ if (state) {
14634
+ state.internal.active = false;
14635
+ const unregisterRoot = state.internal.unregisterRoot;
14636
+ if (unregisterRoot) {
14637
+ unregisterRoot();
14638
+ state.internal.unregisterRoot = void 0;
14639
+ }
14640
+ }
14580
14641
  reconciler.updateContainer(null, fiber, null, () => {
14581
14642
  if (state) {
14582
14643
  setTimeout(() => {
14583
14644
  try {
14584
14645
  const renderer = state.internal.actualRenderer;
14585
- const unregisterRoot = state.internal.unregisterRoot;
14586
- if (unregisterRoot) unregisterRoot();
14587
14646
  const unregisterPrimary = state.internal.unregisterPrimary;
14588
14647
  if (unregisterPrimary) unregisterPrimary();
14589
14648
  const canvasTarget = state.internal.canvasTarget;
14590
- if (canvasTarget?.dispose) canvasTarget.dispose();
14649
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14591
14650
  state.events.disconnect?.();
14592
14651
  cleanupHelperGroup(root.store);
14593
14652
  if (state.isLegacy && renderer) {
@@ -15031,9 +15090,9 @@ function CanvasImpl({
15031
15090
  React__namespace.useMemo(() => extend(THREE), []);
15032
15091
  const Bridge = useBridge();
15033
15092
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15034
- const hasInitialSizeRef = React__namespace.useRef(false);
15093
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15035
15094
  const measureConfig = React__namespace.useMemo(() => {
15036
- if (!hasInitialSizeRef.current) {
15095
+ if (!hasInitialSize) {
15037
15096
  return {
15038
15097
  ...resize,
15039
15098
  scroll: resize?.scroll ?? true,
@@ -15045,7 +15104,7 @@ function CanvasImpl({
15045
15104
  debounce: { scroll: 50, resize: 0 },
15046
15105
  ...resize
15047
15106
  };
15048
- }, [resize, hasInitialSizeRef.current]);
15107
+ }, [hasInitialSize, resize]);
15049
15108
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15050
15109
  const effectiveSize = React__namespace.useMemo(() => {
15051
15110
  let w = width ?? containerRect.width;
@@ -15061,9 +15120,7 @@ function CanvasImpl({
15061
15120
  left: containerRect.left
15062
15121
  };
15063
15122
  }, [width, height, containerRect, forceEven]);
15064
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15065
- hasInitialSizeRef.current = true;
15066
- }
15123
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15067
15124
  const canvasRef = React__namespace.useRef(null);
15068
15125
  const divRef = React__namespace.useRef(null);
15069
15126
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15177,6 +15234,7 @@ function CanvasImpl({
15177
15234
  const canvas = canvasRef.current;
15178
15235
  if (canvas) {
15179
15236
  return () => {
15237
+ if (canvas.isConnected) return;
15180
15238
  unmountComponentAtNode(canvas);
15181
15239
  root.current = null;
15182
15240
  };