@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/legacy.mjs CHANGED
@@ -11,6 +11,7 @@ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
11
11
  import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
12
12
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
13
13
  import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
14
+ import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js';
14
15
  import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
15
16
  import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
16
17
  import { GainMapLoader } from '@monogrid/gainmap-js';
@@ -33,11 +34,11 @@ const R3F_BUILD_WEBGPU = false;
33
34
  async function loadInspector() {
34
35
  throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
35
36
  }
36
- const WebGPURenderer = class WebGPURenderer2 {
37
+ class WebGPURenderer {
37
38
  constructor() {
38
39
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
39
40
  }
40
- };
41
+ }
41
42
  const RenderTarget = null;
42
43
  const CubeRenderTarget = null;
43
44
 
@@ -513,8 +514,10 @@ function hasVisibilityHandlers(handlers) {
513
514
  }
514
515
 
515
516
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
516
- function fromRef(ref) {
517
- return { [FROM_REF]: ref };
517
+ function fromRef(ref, transform) {
518
+ const marker = { [FROM_REF]: ref };
519
+ if (transform) marker.transform = transform;
520
+ return marker;
518
521
  }
519
522
  function isFromRef(value) {
520
523
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -615,11 +618,9 @@ function diffProps(instance, newProps) {
615
618
  for (const prop in instance.props) {
616
619
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
617
620
  const { root, key } = resolve(instance.object, prop);
618
- if (root.constructor && root.constructor.length === 0) {
621
+ if (typeof root?.constructor === "function") {
619
622
  const ctor = getMemoizedPrototype(root);
620
623
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
621
- } else {
622
- changedProps[prop] = 0;
623
624
  }
624
625
  }
625
626
  return changedProps;
@@ -909,7 +910,7 @@ const createStore = (invalidate, advance) => {
909
910
  size: newSize,
910
911
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
911
912
  }));
912
- getScheduler().invalidate();
913
+ get().invalidate();
913
914
  }
914
915
  }
915
916
  return;
@@ -924,15 +925,16 @@ const createStore = (invalidate, advance) => {
924
925
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
925
926
  _sizeImperative: true
926
927
  }));
927
- getScheduler().invalidate();
928
+ get().invalidate();
928
929
  },
929
930
  setDpr: (dpr) => set((state2) => {
930
931
  const resolved = calculateDpr(dpr);
931
932
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
932
933
  }),
933
934
  setFrameloop: (frameloop = "always") => {
935
+ const rootId = get().internal.rootId;
936
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
934
937
  set(() => ({ frameloop }));
935
- getScheduler().frameloop = frameloop;
936
938
  },
937
939
  setError: (error) => set(() => ({ error })),
938
940
  error: null,
@@ -1051,7 +1053,8 @@ const createStore = (invalidate, advance) => {
1051
1053
  const resizeTarget = canvasTarget ?? actualRenderer;
1052
1054
  if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1053
1055
  resizeTarget.setSize(size.width, size.height, false);
1054
- internal.canvasTargetSizeDirty = true;
1056
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1057
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1055
1058
  }
1056
1059
  if (camera !== oldCamera) {
1057
1060
  oldCamera = camera;
@@ -1237,6 +1240,13 @@ function useFrame(callback, priorityOrOptions) {
1237
1240
  * Use for controlling the entire frame loop, adding phases, etc.
1238
1241
  */
1239
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
+ },
1240
1250
  /**
1241
1251
  * Manually step this job only.
1242
1252
  * Bypasses FPS limiting - always runs.
@@ -1253,6 +1263,14 @@ function useFrame(callback, priorityOrOptions) {
1253
1263
  stepAll: (timestamp) => {
1254
1264
  getScheduler().step(timestamp);
1255
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
+ },
1256
1274
  /**
1257
1275
  * Pause this job (set enabled=false).
1258
1276
  * Job remains registered but won't run.
@@ -1325,12 +1343,24 @@ function useTexture(input, optionsOrOnLoad) {
1325
1343
  TextureLoader,
1326
1344
  IsObject(stableInput) ? Object.values(stableInput) : stableInput
1327
1345
  );
1346
+ const mappedTextures = useMemo(() => {
1347
+ if (cachedResult) return cachedResult;
1348
+ if (IsObject(stableInput)) {
1349
+ const keyed = {};
1350
+ const textureArray = loadedTextures;
1351
+ let i = 0;
1352
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1353
+ return keyed;
1354
+ } else {
1355
+ return loadedTextures;
1356
+ }
1357
+ }, [stableInput, loadedTextures, cachedResult]);
1328
1358
  useLayoutEffect(() => {
1329
1359
  if (cachedResult) return;
1330
1360
  if (onLoadCalledForRef.current === inputKey) return;
1331
1361
  onLoadCalledForRef.current = inputKey;
1332
- onLoadRef.current?.(loadedTextures);
1333
- }, [cachedResult, loadedTextures, inputKey]);
1362
+ onLoadRef.current?.(mappedTextures);
1363
+ }, [cachedResult, mappedTextures, inputKey]);
1334
1364
  useEffect(() => {
1335
1365
  if (cachedResult) return;
1336
1366
  if ("initTexture" in renderer) {
@@ -1349,18 +1379,6 @@ function useTexture(input, optionsOrOnLoad) {
1349
1379
  });
1350
1380
  }
1351
1381
  }, [renderer, loadedTextures, cachedResult]);
1352
- const mappedTextures = useMemo(() => {
1353
- if (cachedResult) return cachedResult;
1354
- if (IsObject(stableInput)) {
1355
- const keyed = {};
1356
- const textureArray = loadedTextures;
1357
- let i = 0;
1358
- for (const key in stableInput) keyed[key] = textureArray[i++];
1359
- return keyed;
1360
- } else {
1361
- return loadedTextures;
1362
- }
1363
- }, [stableInput, loadedTextures, cachedResult]);
1364
1382
  useEffect(() => {
1365
1383
  if (!cache) return;
1366
1384
  const urlTextureMap = [];
@@ -1562,7 +1580,7 @@ function useEnvironment({
1562
1580
  texture = texture.renderTarget?.texture;
1563
1581
  }
1564
1582
  texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
1565
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1583
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1566
1584
  return texture;
1567
1585
  }
1568
1586
  const preloadDefaultOptions = {
@@ -1615,11 +1633,12 @@ function getExtension(files) {
1615
1633
  const isCubemap = isArray(files) && files.length === 6;
1616
1634
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1617
1635
  const firstEntry = isArray(files) ? files[0] : files;
1618
- 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();
1636
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1637
+ 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;
1619
1638
  return { extension, isCubemap, isGainmap };
1620
1639
  }
1621
1640
  function getLoader(extension) {
1622
- const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1641
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1623
1642
  return loader;
1624
1643
  }
1625
1644
 
@@ -1670,13 +1689,19 @@ function addTail(callback) {
1670
1689
  return getScheduler().onIdle(callback);
1671
1690
  }
1672
1691
  function invalidate(state, frames = 1, stackFrames = false) {
1673
- getScheduler().invalidate(frames, stackFrames);
1692
+ const rootId = state?.internal?.rootId;
1693
+ const scheduler = getScheduler();
1694
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1695
+ else scheduler.invalidate(frames, stackFrames);
1674
1696
  }
1675
- function advance(timestamp) {
1676
- getScheduler().step(timestamp);
1697
+ function advance(timestamp, runGlobalEffects, state) {
1698
+ const rootId = state?.internal?.rootId;
1699
+ const scheduler = getScheduler();
1700
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1701
+ else scheduler.step(timestamp);
1677
1702
  }
1678
1703
 
1679
- const version = "10.0.0-alpha.3";
1704
+ const version = "10.0.0-alpha.5";
1680
1705
  const packageData = {
1681
1706
  version: version};
1682
1707
 
@@ -13535,7 +13560,10 @@ function extend(objects) {
13535
13560
  catalogue[Component] = objects;
13536
13561
  return Component;
13537
13562
  } else {
13538
- Object.assign(catalogue, objects);
13563
+ for (const name in objects) {
13564
+ const object = objects[name];
13565
+ if (isConstructor(object)) catalogue[name] = object;
13566
+ }
13539
13567
  }
13540
13568
  }
13541
13569
  function validateInstance(type, props) {
@@ -13883,7 +13911,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13883
13911
  getRootHostContext: () => NO_CONTEXT,
13884
13912
  getChildHostContext: () => NO_CONTEXT,
13885
13913
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13886
- validateInstance(type, newProps);
13914
+ validateInstance(instance.type, newProps);
13887
13915
  let reconstruct = false;
13888
13916
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13889
13917
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13910,8 +13938,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13910
13938
  for (const prop in instance.props) {
13911
13939
  const value = instance.props[prop];
13912
13940
  if (isFromRef(value)) {
13913
- const ref = value[FROM_REF];
13914
- if (ref.current != null) resolved[prop] = ref.current;
13941
+ const { [FROM_REF]: ref, transform } = value;
13942
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13915
13943
  }
13916
13944
  }
13917
13945
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14288,7 +14316,7 @@ function createRoot(canvas) {
14288
14316
  const handleXRFrame = (timestamp, _frame) => {
14289
14317
  const state2 = store.getState();
14290
14318
  if (state2.frameloop === "never") return;
14291
- advance(timestamp);
14319
+ advance(timestamp, true, state2);
14292
14320
  };
14293
14321
  const actualRenderer = state.internal.actualRenderer;
14294
14322
  const handleSessionChange = () => {
@@ -14296,7 +14324,7 @@ function createRoot(canvas) {
14296
14324
  const renderer2 = state2.internal.actualRenderer;
14297
14325
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14298
14326
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14299
- if (!renderer2.xr.isPresenting) invalidate();
14327
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14300
14328
  };
14301
14329
  const xr = {
14302
14330
  connect() {
@@ -14378,14 +14406,19 @@ function createRoot(canvas) {
14378
14406
  const newRootId = canvasId || scheduler.generateRootId();
14379
14407
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14380
14408
  getState: () => store.getState(),
14381
- onError: (err) => store.getState().setError(err)
14409
+ onError: (err) => store.getState().setError(err),
14410
+ frameloop: store.getState().frameloop,
14411
+ before: schedulerConfig?.before,
14412
+ after: schedulerConfig?.after,
14413
+ order: schedulerConfig?.order
14382
14414
  });
14383
14415
  const unregisterCanvasTarget = scheduler.register(
14384
14416
  () => {
14385
14417
  const state2 = store.getState();
14386
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14418
+ const canvasTarget = state2.internal.canvasTarget;
14419
+ if (canvasTarget) {
14387
14420
  const renderer2 = state2.internal.actualRenderer;
14388
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14421
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14389
14422
  if (state2.internal.canvasTargetSizeDirty) {
14390
14423
  state2.internal.canvasTargetSizeDirty = false;
14391
14424
  renderer2.backend?.updateSize?.();
@@ -14458,8 +14491,7 @@ function createRoot(canvas) {
14458
14491
  phase: "render",
14459
14492
  system: true,
14460
14493
  // Internal flag: this is a system job, not user-controlled
14461
- // Apply scheduler config for render ordering and rate limiting
14462
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14494
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14463
14495
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14464
14496
  }
14465
14497
  );
@@ -14478,8 +14510,21 @@ function createRoot(canvas) {
14478
14510
  scheduler
14479
14511
  }
14480
14512
  }));
14513
+ } else {
14514
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14515
+ if (constraintsChanged) {
14516
+ scheduler.setRootConstraints(rootId, {
14517
+ before: schedulerConfig?.before,
14518
+ after: schedulerConfig?.after
14519
+ });
14520
+ }
14521
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14522
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14523
+ }
14481
14524
  }
14482
- scheduler.frameloop = frameloop;
14525
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14526
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14527
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14483
14528
  onCreated = onCreatedCallback;
14484
14529
  configured = true;
14485
14530
  resolve();
@@ -14521,18 +14566,23 @@ function unmountComponentAtNode(canvas, callback) {
14521
14566
  const fiber = root?.fiber;
14522
14567
  if (fiber) {
14523
14568
  const state = root?.store.getState();
14524
- if (state) state.internal.active = false;
14569
+ if (state) {
14570
+ state.internal.active = false;
14571
+ const unregisterRoot = state.internal.unregisterRoot;
14572
+ if (unregisterRoot) {
14573
+ unregisterRoot();
14574
+ state.internal.unregisterRoot = void 0;
14575
+ }
14576
+ }
14525
14577
  reconciler.updateContainer(null, fiber, null, () => {
14526
14578
  if (state) {
14527
14579
  setTimeout(() => {
14528
14580
  try {
14529
14581
  const renderer = state.internal.actualRenderer;
14530
- const unregisterRoot = state.internal.unregisterRoot;
14531
- if (unregisterRoot) unregisterRoot();
14532
14582
  const unregisterPrimary = state.internal.unregisterPrimary;
14533
14583
  if (unregisterPrimary) unregisterPrimary();
14534
14584
  const canvasTarget = state.internal.canvasTarget;
14535
- if (canvasTarget?.dispose) canvasTarget.dispose();
14585
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14536
14586
  state.events.disconnect?.();
14537
14587
  cleanupHelperGroup(root.store);
14538
14588
  if (state.isLegacy && renderer) {
@@ -14673,16 +14723,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14673
14723
  const oldbg = target.background;
14674
14724
  const oldenv = target.environment;
14675
14725
  const oldSceneProps = {
14676
- // @ts-ignore
14677
14726
  backgroundBlurriness: target.backgroundBlurriness,
14678
- // @ts-ignore
14679
14727
  backgroundIntensity: target.backgroundIntensity,
14680
- // @ts-ignore
14681
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14682
- // @ts-ignore
14728
+ backgroundRotation: target.backgroundRotation.clone(),
14683
14729
  environmentIntensity: target.environmentIntensity,
14684
- // @ts-ignore
14685
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14730
+ environmentRotation: target.environmentRotation.clone()
14686
14731
  };
14687
14732
  if (background !== "only") target.environment = texture;
14688
14733
  if (background) target.background = texture;
@@ -14976,9 +15021,9 @@ function CanvasImpl({
14976
15021
  React.useMemo(() => extend(THREE), []);
14977
15022
  const Bridge = useBridge();
14978
15023
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
14979
- const hasInitialSizeRef = React.useRef(false);
15024
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
14980
15025
  const measureConfig = React.useMemo(() => {
14981
- if (!hasInitialSizeRef.current) {
15026
+ if (!hasInitialSize) {
14982
15027
  return {
14983
15028
  ...resize,
14984
15029
  scroll: resize?.scroll ?? true,
@@ -14990,7 +15035,7 @@ function CanvasImpl({
14990
15035
  debounce: { scroll: 50, resize: 0 },
14991
15036
  ...resize
14992
15037
  };
14993
- }, [resize, hasInitialSizeRef.current]);
15038
+ }, [hasInitialSize, resize]);
14994
15039
  const [containerRef, containerRect] = useMeasure(measureConfig);
14995
15040
  const effectiveSize = React.useMemo(() => {
14996
15041
  let w = width ?? containerRect.width;
@@ -15006,9 +15051,7 @@ function CanvasImpl({
15006
15051
  left: containerRect.left
15007
15052
  };
15008
15053
  }, [width, height, containerRect, forceEven]);
15009
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15010
- hasInitialSizeRef.current = true;
15011
- }
15054
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15012
15055
  const canvasRef = React.useRef(null);
15013
15056
  const divRef = React.useRef(null);
15014
15057
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15122,6 +15165,7 @@ function CanvasImpl({
15122
15165
  const canvas = canvasRef.current;
15123
15166
  if (canvas) {
15124
15167
  return () => {
15168
+ if (canvas.isConnected) return;
15125
15169
  unmountComponentAtNode(canvas);
15126
15170
  root.current = null;
15127
15171
  };