@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/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as webgpu from 'three/webgpu';
2
2
  import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color } from 'three/webgpu';
3
3
  import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
4
- import { Inspector } from 'three/addons/inspector/Inspector.js';
5
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
5
  import * as React from 'react';
7
6
  import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -32,18 +31,22 @@ function _mergeNamespaces(n, m) {
32
31
 
33
32
  const R3F_BUILD_LEGACY = true;
34
33
  const R3F_BUILD_WEBGPU = true;
34
+ async function loadInspector() {
35
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
36
+ return Inspector;
37
+ }
35
38
 
36
39
  const THREE = /*#__PURE__*/_mergeNamespaces({
37
40
  __proto__: null,
38
41
  CubeRenderTarget: CubeRenderTarget,
39
42
  CubeRenderTargetCompat: CubeRenderTarget,
40
- Inspector: Inspector,
41
43
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
42
44
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
43
45
  RenderTargetCompat: RenderTarget,
44
46
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
45
47
  WebGLRenderTarget: WebGLRenderTarget,
46
- WebGLRenderer: WebGLRenderer
48
+ WebGLRenderer: WebGLRenderer,
49
+ loadInspector: loadInspector
47
50
  }, [webgpu]);
48
51
 
49
52
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -599,7 +602,12 @@ function diffProps(instance, newProps) {
599
602
  const changedProps = {};
600
603
  for (const prop in newProps) {
601
604
  if (RESERVED_PROPS.includes(prop)) continue;
602
- if (is.equ(newProps[prop], instance.props[prop])) continue;
605
+ if (is.equ(newProps[prop], instance.props[prop])) {
606
+ const value = newProps[prop];
607
+ if (!isCopyable(value)) continue;
608
+ const { target } = resolve(instance.object, prop);
609
+ if (!isCopyable(target) || target.constructor !== value.constructor) continue;
610
+ }
603
611
  changedProps[prop] = newProps[prop];
604
612
  for (const other in newProps) {
605
613
  if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
@@ -902,7 +910,7 @@ const createStore = (invalidate, advance) => {
902
910
  size: newSize,
903
911
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
904
912
  }));
905
- getScheduler().invalidate();
913
+ get().invalidate();
906
914
  }
907
915
  }
908
916
  return;
@@ -917,15 +925,16 @@ const createStore = (invalidate, advance) => {
917
925
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
918
926
  _sizeImperative: true
919
927
  }));
920
- getScheduler().invalidate();
928
+ get().invalidate();
921
929
  },
922
930
  setDpr: (dpr) => set((state2) => {
923
931
  const resolved = calculateDpr(dpr);
924
932
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
925
933
  }),
926
934
  setFrameloop: (frameloop = "always") => {
935
+ const rootId = get().internal.rootId;
936
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
927
937
  set(() => ({ frameloop }));
928
- getScheduler().frameloop = frameloop;
929
938
  },
930
939
  setError: (error) => set(() => ({ error })),
931
940
  error: null,
@@ -1041,17 +1050,11 @@ const createStore = (invalidate, advance) => {
1041
1050
  oldSize = size;
1042
1051
  oldDpr = viewport.dpr;
1043
1052
  updateCamera(camera, size);
1044
- if (internal.isSecondary && canvasTarget) {
1045
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1046
- canvasTarget.setSize(size.width, size.height, false);
1047
- } else {
1048
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1049
- actualRenderer.setSize(size.width, size.height, false);
1050
- if (canvasTarget) {
1051
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1052
- canvasTarget.setSize(size.width, size.height, false);
1053
- }
1054
- }
1053
+ const resizeTarget = canvasTarget ?? actualRenderer;
1054
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1055
+ resizeTarget.setSize(size.width, size.height, false);
1056
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1057
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1055
1058
  }
1056
1059
  if (camera !== oldCamera) {
1057
1060
  oldCamera = camera;
@@ -1113,7 +1116,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1113
1116
  const results = cacheKeys.map(
1114
1117
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1115
1118
  );
1116
- return Array.isArray(input) ? results : results[0];
1119
+ const stableRef = useRef(null);
1120
+ const previous = stableRef.current;
1121
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1122
+ stableRef.current = stable;
1123
+ return Array.isArray(input) ? stable : stable[0];
1117
1124
  }
1118
1125
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1119
1126
  const inputs = Array.isArray(input) ? input : [input];
@@ -1233,6 +1240,13 @@ function useFrame(callback, priorityOrOptions) {
1233
1240
  * Use for controlling the entire frame loop, adding phases, etc.
1234
1241
  */
1235
1242
  scheduler: scheduler2,
1243
+ /**
1244
+ * The root that currently owns this job. Resolve on access because ambient
1245
+ * jobs can be adopted by a Canvas after the controls object is created.
1246
+ */
1247
+ get rootId() {
1248
+ return getScheduler().getJobRootId(id);
1249
+ },
1236
1250
  /**
1237
1251
  * Manually step this job only.
1238
1252
  * Bypasses FPS limiting - always runs.
@@ -1249,6 +1263,14 @@ function useFrame(callback, priorityOrOptions) {
1249
1263
  stepAll: (timestamp) => {
1250
1264
  getScheduler().step(timestamp);
1251
1265
  },
1266
+ /**
1267
+ * Request frames for this job's owning root without waking sibling roots.
1268
+ */
1269
+ invalidate: (frames, stackFrames) => {
1270
+ const scheduler3 = getScheduler();
1271
+ const rootId = scheduler3.getJobRootId(id);
1272
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1273
+ },
1252
1274
  /**
1253
1275
  * Pause this job (set enabled=false).
1254
1276
  * Job remains registered but won't run.
@@ -1278,6 +1300,11 @@ function getUrls(input) {
1278
1300
  if (Array.isArray(input)) return input;
1279
1301
  return Object.values(input);
1280
1302
  }
1303
+ function inputSignature(input) {
1304
+ if (typeof input === "string") return input;
1305
+ if (Array.isArray(input)) return input.join("\0");
1306
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1307
+ }
1281
1308
  function allUrlsCached(urls, textureCache) {
1282
1309
  return urls.every((url) => textureCache.has(url));
1283
1310
  }
@@ -1303,18 +1330,19 @@ function useTexture(input, optionsOrOnLoad) {
1303
1330
  const onLoadRef = useRef(onLoad);
1304
1331
  onLoadRef.current = onLoad;
1305
1332
  const onLoadCalledForRef = useRef(null);
1306
- const urls = useMemo(() => getUrls(input), [input]);
1333
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1334
+ const stableInput = useMemo(() => input, [inputKey]);
1335
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1307
1336
  const cachedResult = useMemo(() => {
1308
1337
  if (!cache) return null;
1309
1338
  const textures = store.getState().textures;
1310
1339
  if (!allUrlsCached(urls, textures)) return null;
1311
- return buildFromCache(input, textures);
1312
- }, [cache, urls, input, store]);
1340
+ return buildFromCache(stableInput, textures);
1341
+ }, [cache, urls, stableInput, store]);
1313
1342
  const loadedTextures = useLoader(
1314
1343
  TextureLoader,
1315
- IsObject(input) ? Object.values(input) : input
1344
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1316
1345
  );
1317
- const inputKey = urls.join("\0");
1318
1346
  useLayoutEffect(() => {
1319
1347
  if (cachedResult) return;
1320
1348
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1341,28 +1369,28 @@ function useTexture(input, optionsOrOnLoad) {
1341
1369
  }, [renderer, loadedTextures, cachedResult]);
1342
1370
  const mappedTextures = useMemo(() => {
1343
1371
  if (cachedResult) return cachedResult;
1344
- if (IsObject(input)) {
1372
+ if (IsObject(stableInput)) {
1345
1373
  const keyed = {};
1346
1374
  const textureArray = loadedTextures;
1347
1375
  let i = 0;
1348
- for (const key in input) keyed[key] = textureArray[i++];
1376
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1349
1377
  return keyed;
1350
1378
  } else {
1351
1379
  return loadedTextures;
1352
1380
  }
1353
- }, [input, loadedTextures, cachedResult]);
1381
+ }, [stableInput, loadedTextures, cachedResult]);
1354
1382
  useEffect(() => {
1355
1383
  if (!cache) return;
1356
1384
  const urlTextureMap = [];
1357
- if (typeof input === "string") {
1358
- urlTextureMap.push([input, mappedTextures]);
1359
- } else if (Array.isArray(input)) {
1385
+ if (typeof stableInput === "string") {
1386
+ urlTextureMap.push([stableInput, mappedTextures]);
1387
+ } else if (Array.isArray(stableInput)) {
1360
1388
  const textureArray = mappedTextures;
1361
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1362
- } else if (IsObject(input)) {
1389
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1390
+ } else if (IsObject(stableInput)) {
1363
1391
  const textureRecord = mappedTextures;
1364
- for (const key in input) {
1365
- const url = input[key];
1392
+ for (const key in stableInput) {
1393
+ const url = stableInput[key];
1366
1394
  urlTextureMap.push([url, textureRecord[key]]);
1367
1395
  }
1368
1396
  }
@@ -1391,7 +1419,7 @@ function useTexture(input, optionsOrOnLoad) {
1391
1419
  }
1392
1420
  return { _textureRefs: refs };
1393
1421
  });
1394
- }, [cache, input, mappedTextures, store]);
1422
+ }, [cache, stableInput, mappedTextures, store]);
1395
1423
  return mappedTextures;
1396
1424
  }
1397
1425
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1662,13 +1690,19 @@ function addTail(callback) {
1662
1690
  return getScheduler().onIdle(callback);
1663
1691
  }
1664
1692
  function invalidate(state, frames = 1, stackFrames = false) {
1665
- getScheduler().invalidate(frames, stackFrames);
1693
+ const rootId = state?.internal?.rootId;
1694
+ const scheduler = getScheduler();
1695
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1696
+ else scheduler.invalidate(frames, stackFrames);
1666
1697
  }
1667
- function advance(timestamp) {
1668
- getScheduler().step(timestamp);
1698
+ function advance(timestamp, runGlobalEffects, state) {
1699
+ const rootId = state?.internal?.rootId;
1700
+ const scheduler = getScheduler();
1701
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1702
+ else scheduler.step(timestamp);
1669
1703
  }
1670
1704
 
1671
- const version = "10.0.0-alpha.3";
1705
+ const version = "10.0.0-alpha.4";
1672
1706
  const packageData = {
1673
1707
  version: version};
1674
1708
 
@@ -14202,9 +14236,8 @@ function createRoot(canvas) {
14202
14236
  if (!renderer.hasInitialized?.()) {
14203
14237
  const size2 = computeInitialSize(canvas, propsSize);
14204
14238
  if (size2.width > 0 && size2.height > 0) {
14205
- const pixelRatio = calculateDpr(dpr);
14206
- canvas.width = size2.width * pixelRatio;
14207
- canvas.height = size2.height * pixelRatio;
14239
+ renderer.setPixelRatio(calculateDpr(dpr));
14240
+ renderer.setSize(size2.width, size2.height, false);
14208
14241
  }
14209
14242
  await renderer.init();
14210
14243
  }
@@ -14213,7 +14246,7 @@ function createRoot(canvas) {
14213
14246
  state.internal.actualRenderer = renderer;
14214
14247
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14215
14248
  if (canvasId && !state.internal.isSecondary) {
14216
- const canvasTarget = new CanvasTarget(canvas);
14249
+ const canvasTarget = renderer.getCanvasTarget?.();
14217
14250
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14218
14251
  state.set((prev) => ({
14219
14252
  internal: {
@@ -14327,7 +14360,7 @@ function createRoot(canvas) {
14327
14360
  const handleXRFrame = (timestamp, _frame) => {
14328
14361
  const state2 = store.getState();
14329
14362
  if (state2.frameloop === "never") return;
14330
- advance(timestamp);
14363
+ advance(timestamp, true, state2);
14331
14364
  };
14332
14365
  const actualRenderer = state.internal.actualRenderer;
14333
14366
  const handleSessionChange = () => {
@@ -14335,7 +14368,7 @@ function createRoot(canvas) {
14335
14368
  const renderer2 = state2.internal.actualRenderer;
14336
14369
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14337
14370
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14338
- if (!renderer2.xr.isPresenting) invalidate();
14371
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14339
14372
  };
14340
14373
  const xr = {
14341
14374
  connect() {
@@ -14417,14 +14450,23 @@ function createRoot(canvas) {
14417
14450
  const newRootId = canvasId || scheduler.generateRootId();
14418
14451
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14419
14452
  getState: () => store.getState(),
14420
- onError: (err) => store.getState().setError(err)
14453
+ onError: (err) => store.getState().setError(err),
14454
+ frameloop: store.getState().frameloop,
14455
+ before: schedulerConfig?.before,
14456
+ after: schedulerConfig?.after,
14457
+ order: schedulerConfig?.order
14421
14458
  });
14422
14459
  const unregisterCanvasTarget = scheduler.register(
14423
14460
  () => {
14424
14461
  const state2 = store.getState();
14425
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14462
+ const canvasTarget = state2.internal.canvasTarget;
14463
+ if (canvasTarget) {
14426
14464
  const renderer2 = state2.internal.actualRenderer;
14427
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14465
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14466
+ if (state2.internal.canvasTargetSizeDirty) {
14467
+ state2.internal.canvasTargetSizeDirty = false;
14468
+ renderer2.backend?.updateSize?.();
14469
+ }
14428
14470
  }
14429
14471
  },
14430
14472
  {
@@ -14493,8 +14535,7 @@ function createRoot(canvas) {
14493
14535
  phase: "render",
14494
14536
  system: true,
14495
14537
  // Internal flag: this is a system job, not user-controlled
14496
- // Apply scheduler config for render ordering and rate limiting
14497
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14538
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14498
14539
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14499
14540
  }
14500
14541
  );
@@ -14513,8 +14554,21 @@ function createRoot(canvas) {
14513
14554
  scheduler
14514
14555
  }
14515
14556
  }));
14557
+ } else {
14558
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14559
+ if (constraintsChanged) {
14560
+ scheduler.setRootConstraints(rootId, {
14561
+ before: schedulerConfig?.before,
14562
+ after: schedulerConfig?.after
14563
+ });
14564
+ }
14565
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14566
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14567
+ }
14516
14568
  }
14517
- scheduler.frameloop = frameloop;
14569
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14570
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14571
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14518
14572
  onCreated = onCreatedCallback;
14519
14573
  configured = true;
14520
14574
  resolve();
@@ -14556,18 +14610,23 @@ function unmountComponentAtNode(canvas, callback) {
14556
14610
  const fiber = root?.fiber;
14557
14611
  if (fiber) {
14558
14612
  const state = root?.store.getState();
14559
- if (state) state.internal.active = false;
14613
+ if (state) {
14614
+ state.internal.active = false;
14615
+ const unregisterRoot = state.internal.unregisterRoot;
14616
+ if (unregisterRoot) {
14617
+ unregisterRoot();
14618
+ state.internal.unregisterRoot = void 0;
14619
+ }
14620
+ }
14560
14621
  reconciler.updateContainer(null, fiber, null, () => {
14561
14622
  if (state) {
14562
14623
  setTimeout(() => {
14563
14624
  try {
14564
14625
  const renderer = state.internal.actualRenderer;
14565
- const unregisterRoot = state.internal.unregisterRoot;
14566
- if (unregisterRoot) unregisterRoot();
14567
14626
  const unregisterPrimary = state.internal.unregisterPrimary;
14568
14627
  if (unregisterPrimary) unregisterPrimary();
14569
14628
  const canvasTarget = state.internal.canvasTarget;
14570
- if (canvasTarget?.dispose) canvasTarget.dispose();
14629
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14571
14630
  state.events.disconnect?.();
14572
14631
  cleanupHelperGroup(root.store);
14573
14632
  if (state.isLegacy && renderer) {
@@ -15011,9 +15070,9 @@ function CanvasImpl({
15011
15070
  React.useMemo(() => extend(THREE), []);
15012
15071
  const Bridge = useBridge();
15013
15072
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15014
- const hasInitialSizeRef = React.useRef(false);
15073
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15015
15074
  const measureConfig = React.useMemo(() => {
15016
- if (!hasInitialSizeRef.current) {
15075
+ if (!hasInitialSize) {
15017
15076
  return {
15018
15077
  ...resize,
15019
15078
  scroll: resize?.scroll ?? true,
@@ -15025,7 +15084,7 @@ function CanvasImpl({
15025
15084
  debounce: { scroll: 50, resize: 0 },
15026
15085
  ...resize
15027
15086
  };
15028
- }, [resize, hasInitialSizeRef.current]);
15087
+ }, [hasInitialSize, resize]);
15029
15088
  const [containerRef, containerRect] = useMeasure(measureConfig);
15030
15089
  const effectiveSize = React.useMemo(() => {
15031
15090
  let w = width ?? containerRect.width;
@@ -15041,9 +15100,7 @@ function CanvasImpl({
15041
15100
  left: containerRect.left
15042
15101
  };
15043
15102
  }, [width, height, containerRect, forceEven]);
15044
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15045
- hasInitialSizeRef.current = true;
15046
- }
15103
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15047
15104
  const canvasRef = React.useRef(null);
15048
15105
  const divRef = React.useRef(null);
15049
15106
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15157,6 +15214,7 @@ function CanvasImpl({
15157
15214
  const canvas = canvasRef.current;
15158
15215
  if (canvas) {
15159
15216
  return () => {
15217
+ if (canvas.isConnected) return;
15160
15218
  unmountComponentAtNode(canvas);
15161
15219
  root.current = null;
15162
15220
  };