@react-three/fiber 10.0.0-canary.20de6b2 → 10.0.0-canary.27df622

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
@@ -12,6 +12,7 @@ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
12
12
  import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
13
13
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
14
14
  import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
15
+ import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js';
15
16
  import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
16
17
  import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
17
18
  import { GainMapLoader } from '@monogrid/gainmap-js';
@@ -509,8 +510,10 @@ function hasVisibilityHandlers(handlers) {
509
510
  }
510
511
 
511
512
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
512
- function fromRef(ref) {
513
- return { [FROM_REF]: ref };
513
+ function fromRef(ref, transform) {
514
+ const marker = { [FROM_REF]: ref };
515
+ if (transform) marker.transform = transform;
516
+ return marker;
514
517
  }
515
518
  function isFromRef(value) {
516
519
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -611,11 +614,9 @@ function diffProps(instance, newProps) {
611
614
  for (const prop in instance.props) {
612
615
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
613
616
  const { root, key } = resolve(instance.object, prop);
614
- if (root.constructor && root.constructor.length === 0) {
617
+ if (typeof root?.constructor === "function") {
615
618
  const ctor = getMemoizedPrototype(root);
616
619
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
617
- } else {
618
- changedProps[prop] = 0;
619
620
  }
620
621
  }
621
622
  return changedProps;
@@ -905,7 +906,7 @@ const createStore = (invalidate, advance) => {
905
906
  size: newSize,
906
907
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
907
908
  }));
908
- getScheduler().invalidate();
909
+ get().invalidate();
909
910
  }
910
911
  }
911
912
  return;
@@ -920,15 +921,16 @@ const createStore = (invalidate, advance) => {
920
921
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
921
922
  _sizeImperative: true
922
923
  }));
923
- getScheduler().invalidate();
924
+ get().invalidate();
924
925
  },
925
926
  setDpr: (dpr) => set((state2) => {
926
927
  const resolved = calculateDpr(dpr);
927
928
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
928
929
  }),
929
930
  setFrameloop: (frameloop = "always") => {
931
+ const rootId = get().internal.rootId;
932
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
930
933
  set(() => ({ frameloop }));
931
- getScheduler().frameloop = frameloop;
932
934
  },
933
935
  setError: (error) => set(() => ({ error })),
934
936
  error: null,
@@ -1047,7 +1049,8 @@ const createStore = (invalidate, advance) => {
1047
1049
  const resizeTarget = canvasTarget ?? actualRenderer;
1048
1050
  if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1049
1051
  resizeTarget.setSize(size.width, size.height, false);
1050
- internal.canvasTargetSizeDirty = true;
1052
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1053
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1051
1054
  }
1052
1055
  if (camera !== oldCamera) {
1053
1056
  oldCamera = camera;
@@ -1233,6 +1236,13 @@ function useFrame(callback, priorityOrOptions) {
1233
1236
  * Use for controlling the entire frame loop, adding phases, etc.
1234
1237
  */
1235
1238
  scheduler: scheduler2,
1239
+ /**
1240
+ * The root that currently owns this job. Resolve on access because ambient
1241
+ * jobs can be adopted by a Canvas after the controls object is created.
1242
+ */
1243
+ get rootId() {
1244
+ return getScheduler().getJobRootId(id);
1245
+ },
1236
1246
  /**
1237
1247
  * Manually step this job only.
1238
1248
  * Bypasses FPS limiting - always runs.
@@ -1249,6 +1259,14 @@ function useFrame(callback, priorityOrOptions) {
1249
1259
  stepAll: (timestamp) => {
1250
1260
  getScheduler().step(timestamp);
1251
1261
  },
1262
+ /**
1263
+ * Request frames for this job's owning root without waking sibling roots.
1264
+ */
1265
+ invalidate: (frames, stackFrames) => {
1266
+ const scheduler3 = getScheduler();
1267
+ const rootId = scheduler3.getJobRootId(id);
1268
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1269
+ },
1252
1270
  /**
1253
1271
  * Pause this job (set enabled=false).
1254
1272
  * Job remains registered but won't run.
@@ -1321,12 +1339,24 @@ function useTexture(input, optionsOrOnLoad) {
1321
1339
  TextureLoader,
1322
1340
  IsObject(stableInput) ? Object.values(stableInput) : stableInput
1323
1341
  );
1342
+ const mappedTextures = useMemo(() => {
1343
+ if (cachedResult) return cachedResult;
1344
+ if (IsObject(stableInput)) {
1345
+ const keyed = {};
1346
+ const textureArray = loadedTextures;
1347
+ let i = 0;
1348
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1349
+ return keyed;
1350
+ } else {
1351
+ return loadedTextures;
1352
+ }
1353
+ }, [stableInput, loadedTextures, cachedResult]);
1324
1354
  useLayoutEffect(() => {
1325
1355
  if (cachedResult) return;
1326
1356
  if (onLoadCalledForRef.current === inputKey) return;
1327
1357
  onLoadCalledForRef.current = inputKey;
1328
- onLoadRef.current?.(loadedTextures);
1329
- }, [cachedResult, loadedTextures, inputKey]);
1358
+ onLoadRef.current?.(mappedTextures);
1359
+ }, [cachedResult, mappedTextures, inputKey]);
1330
1360
  useEffect(() => {
1331
1361
  if (cachedResult) return;
1332
1362
  if ("initTexture" in renderer) {
@@ -1345,18 +1375,6 @@ function useTexture(input, optionsOrOnLoad) {
1345
1375
  });
1346
1376
  }
1347
1377
  }, [renderer, loadedTextures, cachedResult]);
1348
- const mappedTextures = useMemo(() => {
1349
- if (cachedResult) return cachedResult;
1350
- if (IsObject(stableInput)) {
1351
- const keyed = {};
1352
- const textureArray = loadedTextures;
1353
- let i = 0;
1354
- for (const key in stableInput) keyed[key] = textureArray[i++];
1355
- return keyed;
1356
- } else {
1357
- return loadedTextures;
1358
- }
1359
- }, [stableInput, loadedTextures, cachedResult]);
1360
1378
  useEffect(() => {
1361
1379
  if (!cache) return;
1362
1380
  const urlTextureMap = [];
@@ -1560,7 +1578,7 @@ function useEnvironment({
1560
1578
  texture = texture.renderTarget?.texture;
1561
1579
  }
1562
1580
  texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
1563
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1581
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1564
1582
  return texture;
1565
1583
  }
1566
1584
  const preloadDefaultOptions = {
@@ -1613,11 +1631,12 @@ function getExtension(files) {
1613
1631
  const isCubemap = isArray(files) && files.length === 6;
1614
1632
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1615
1633
  const firstEntry = isArray(files) ? files[0] : files;
1616
- const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1634
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1635
+ const extension = isCubemap ? firstExtension === "hdr" ? "hdr-cube" : "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstExtension;
1617
1636
  return { extension, isCubemap, isGainmap };
1618
1637
  }
1619
1638
  function getLoader(extension) {
1620
- const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1639
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1621
1640
  return loader;
1622
1641
  }
1623
1642
 
@@ -1668,13 +1687,19 @@ function addTail(callback) {
1668
1687
  return getScheduler().onIdle(callback);
1669
1688
  }
1670
1689
  function invalidate(state, frames = 1, stackFrames = false) {
1671
- 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);
1672
1694
  }
1673
- function advance(timestamp) {
1674
- 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);
1675
1700
  }
1676
1701
 
1677
- const version = "10.0.0-alpha.3";
1702
+ const version = "10.0.0-alpha.5";
1678
1703
  const packageData = {
1679
1704
  version: version};
1680
1705
 
@@ -13533,7 +13558,10 @@ function extend(objects) {
13533
13558
  catalogue[Component] = objects;
13534
13559
  return Component;
13535
13560
  } else {
13536
- Object.assign(catalogue, objects);
13561
+ for (const name in objects) {
13562
+ const object = objects[name];
13563
+ if (isConstructor(object)) catalogue[name] = object;
13564
+ }
13537
13565
  }
13538
13566
  }
13539
13567
  function validateInstance(type, props) {
@@ -13881,7 +13909,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13881
13909
  getRootHostContext: () => NO_CONTEXT,
13882
13910
  getChildHostContext: () => NO_CONTEXT,
13883
13911
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13884
- validateInstance(type, newProps);
13912
+ validateInstance(instance.type, newProps);
13885
13913
  let reconstruct = false;
13886
13914
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13887
13915
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13908,8 +13936,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13908
13936
  for (const prop in instance.props) {
13909
13937
  const value = instance.props[prop];
13910
13938
  if (isFromRef(value)) {
13911
- const ref = value[FROM_REF];
13912
- if (ref.current != null) resolved[prop] = ref.current;
13939
+ const { [FROM_REF]: ref, transform } = value;
13940
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13913
13941
  }
13914
13942
  }
13915
13943
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14208,9 +14236,8 @@ function createRoot(canvas) {
14208
14236
  if (!renderer.hasInitialized?.()) {
14209
14237
  const size2 = computeInitialSize(canvas, propsSize);
14210
14238
  if (size2.width > 0 && size2.height > 0) {
14211
- const pixelRatio = calculateDpr(dpr);
14212
- canvas.width = size2.width * pixelRatio;
14213
- canvas.height = size2.height * pixelRatio;
14239
+ renderer.setPixelRatio(calculateDpr(dpr));
14240
+ renderer.setSize(size2.width, size2.height, false);
14214
14241
  }
14215
14242
  await renderer.init();
14216
14243
  }
@@ -14219,7 +14246,7 @@ function createRoot(canvas) {
14219
14246
  state.internal.actualRenderer = renderer;
14220
14247
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14221
14248
  if (canvasId && !state.internal.isSecondary) {
14222
- const canvasTarget = new CanvasTarget(canvas);
14249
+ const canvasTarget = renderer.getCanvasTarget?.();
14223
14250
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14224
14251
  state.set((prev) => ({
14225
14252
  internal: {
@@ -14333,7 +14360,7 @@ function createRoot(canvas) {
14333
14360
  const handleXRFrame = (timestamp, _frame) => {
14334
14361
  const state2 = store.getState();
14335
14362
  if (state2.frameloop === "never") return;
14336
- advance(timestamp);
14363
+ advance(timestamp, true, state2);
14337
14364
  };
14338
14365
  const actualRenderer = state.internal.actualRenderer;
14339
14366
  const handleSessionChange = () => {
@@ -14341,7 +14368,7 @@ function createRoot(canvas) {
14341
14368
  const renderer2 = state2.internal.actualRenderer;
14342
14369
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14343
14370
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14344
- if (!renderer2.xr.isPresenting) invalidate();
14371
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14345
14372
  };
14346
14373
  const xr = {
14347
14374
  connect() {
@@ -14423,14 +14450,19 @@ function createRoot(canvas) {
14423
14450
  const newRootId = canvasId || scheduler.generateRootId();
14424
14451
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14425
14452
  getState: () => store.getState(),
14426
- 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
14427
14458
  });
14428
14459
  const unregisterCanvasTarget = scheduler.register(
14429
14460
  () => {
14430
14461
  const state2 = store.getState();
14431
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14462
+ const canvasTarget = state2.internal.canvasTarget;
14463
+ if (canvasTarget) {
14432
14464
  const renderer2 = state2.internal.actualRenderer;
14433
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14465
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14434
14466
  if (state2.internal.canvasTargetSizeDirty) {
14435
14467
  state2.internal.canvasTargetSizeDirty = false;
14436
14468
  renderer2.backend?.updateSize?.();
@@ -14503,8 +14535,7 @@ function createRoot(canvas) {
14503
14535
  phase: "render",
14504
14536
  system: true,
14505
14537
  // Internal flag: this is a system job, not user-controlled
14506
- // Apply scheduler config for render ordering and rate limiting
14507
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14538
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14508
14539
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14509
14540
  }
14510
14541
  );
@@ -14523,8 +14554,21 @@ function createRoot(canvas) {
14523
14554
  scheduler
14524
14555
  }
14525
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
+ }
14526
14568
  }
14527
- scheduler.frameloop = frameloop;
14569
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14570
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14571
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14528
14572
  onCreated = onCreatedCallback;
14529
14573
  configured = true;
14530
14574
  resolve();
@@ -14566,18 +14610,23 @@ function unmountComponentAtNode(canvas, callback) {
14566
14610
  const fiber = root?.fiber;
14567
14611
  if (fiber) {
14568
14612
  const state = root?.store.getState();
14569
- 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
+ }
14570
14621
  reconciler.updateContainer(null, fiber, null, () => {
14571
14622
  if (state) {
14572
14623
  setTimeout(() => {
14573
14624
  try {
14574
14625
  const renderer = state.internal.actualRenderer;
14575
- const unregisterRoot = state.internal.unregisterRoot;
14576
- if (unregisterRoot) unregisterRoot();
14577
14626
  const unregisterPrimary = state.internal.unregisterPrimary;
14578
14627
  if (unregisterPrimary) unregisterPrimary();
14579
14628
  const canvasTarget = state.internal.canvasTarget;
14580
- if (canvasTarget?.dispose) canvasTarget.dispose();
14629
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14581
14630
  state.events.disconnect?.();
14582
14631
  cleanupHelperGroup(root.store);
14583
14632
  if (state.isLegacy && renderer) {
@@ -14718,16 +14767,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14718
14767
  const oldbg = target.background;
14719
14768
  const oldenv = target.environment;
14720
14769
  const oldSceneProps = {
14721
- // @ts-ignore
14722
14770
  backgroundBlurriness: target.backgroundBlurriness,
14723
- // @ts-ignore
14724
14771
  backgroundIntensity: target.backgroundIntensity,
14725
- // @ts-ignore
14726
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14727
- // @ts-ignore
14772
+ backgroundRotation: target.backgroundRotation.clone(),
14728
14773
  environmentIntensity: target.environmentIntensity,
14729
- // @ts-ignore
14730
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14774
+ environmentRotation: target.environmentRotation.clone()
14731
14775
  };
14732
14776
  if (background !== "only") target.environment = texture;
14733
14777
  if (background) target.background = texture;
@@ -15021,9 +15065,9 @@ function CanvasImpl({
15021
15065
  React.useMemo(() => extend(THREE), []);
15022
15066
  const Bridge = useBridge();
15023
15067
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15024
- const hasInitialSizeRef = React.useRef(false);
15068
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15025
15069
  const measureConfig = React.useMemo(() => {
15026
- if (!hasInitialSizeRef.current) {
15070
+ if (!hasInitialSize) {
15027
15071
  return {
15028
15072
  ...resize,
15029
15073
  scroll: resize?.scroll ?? true,
@@ -15035,7 +15079,7 @@ function CanvasImpl({
15035
15079
  debounce: { scroll: 50, resize: 0 },
15036
15080
  ...resize
15037
15081
  };
15038
- }, [resize, hasInitialSizeRef.current]);
15082
+ }, [hasInitialSize, resize]);
15039
15083
  const [containerRef, containerRect] = useMeasure(measureConfig);
15040
15084
  const effectiveSize = React.useMemo(() => {
15041
15085
  let w = width ?? containerRect.width;
@@ -15051,9 +15095,7 @@ function CanvasImpl({
15051
15095
  left: containerRect.left
15052
15096
  };
15053
15097
  }, [width, height, containerRect, forceEven]);
15054
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15055
- hasInitialSizeRef.current = true;
15056
- }
15098
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15057
15099
  const canvasRef = React.useRef(null);
15058
15100
  const divRef = React.useRef(null);
15059
15101
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15167,6 +15209,7 @@ function CanvasImpl({
15167
15209
  const canvas = canvasRef.current;
15168
15210
  if (canvas) {
15169
15211
  return () => {
15212
+ if (canvas.isConnected) return;
15170
15213
  unmountComponentAtNode(canvas);
15171
15214
  root.current = null;
15172
15215
  };