@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.cjs CHANGED
@@ -11,6 +11,7 @@ const scheduler = require('@pmndrs/scheduler');
11
11
  const Tb = require('scheduler');
12
12
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
13
13
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
14
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
14
15
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
15
16
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
16
17
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -529,8 +530,10 @@ function hasVisibilityHandlers(handlers) {
529
530
  }
530
531
 
531
532
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
532
- function fromRef(ref) {
533
- return { [FROM_REF]: ref };
533
+ function fromRef(ref, transform) {
534
+ const marker = { [FROM_REF]: ref };
535
+ if (transform) marker.transform = transform;
536
+ return marker;
534
537
  }
535
538
  function isFromRef(value) {
536
539
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -631,11 +634,9 @@ function diffProps(instance, newProps) {
631
634
  for (const prop in instance.props) {
632
635
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
633
636
  const { root, key } = resolve(instance.object, prop);
634
- if (root.constructor && root.constructor.length === 0) {
637
+ if (typeof root?.constructor === "function") {
635
638
  const ctor = getMemoizedPrototype(root);
636
639
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
637
- } else {
638
- changedProps[prop] = 0;
639
640
  }
640
641
  }
641
642
  return changedProps;
@@ -925,7 +926,7 @@ const createStore = (invalidate, advance) => {
925
926
  size: newSize,
926
927
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
927
928
  }));
928
- scheduler.getScheduler().invalidate();
929
+ get().invalidate();
929
930
  }
930
931
  }
931
932
  return;
@@ -940,15 +941,16 @@ const createStore = (invalidate, advance) => {
940
941
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
941
942
  _sizeImperative: true
942
943
  }));
943
- scheduler.getScheduler().invalidate();
944
+ get().invalidate();
944
945
  },
945
946
  setDpr: (dpr) => set((state2) => {
946
947
  const resolved = calculateDpr(dpr);
947
948
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
948
949
  }),
949
950
  setFrameloop: (frameloop = "always") => {
951
+ const rootId = get().internal.rootId;
952
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
950
953
  set(() => ({ frameloop }));
951
- scheduler.getScheduler().frameloop = frameloop;
952
954
  },
953
955
  setError: (error) => set(() => ({ error })),
954
956
  error: null,
@@ -1067,7 +1069,8 @@ const createStore = (invalidate, advance) => {
1067
1069
  const resizeTarget = canvasTarget ?? actualRenderer;
1068
1070
  if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1069
1071
  resizeTarget.setSize(size.width, size.height, false);
1070
- internal.canvasTargetSizeDirty = true;
1072
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1073
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1071
1074
  }
1072
1075
  if (camera !== oldCamera) {
1073
1076
  oldCamera = camera;
@@ -1253,6 +1256,13 @@ function useFrame(callback, priorityOrOptions) {
1253
1256
  * Use for controlling the entire frame loop, adding phases, etc.
1254
1257
  */
1255
1258
  scheduler: scheduler2,
1259
+ /**
1260
+ * The root that currently owns this job. Resolve on access because ambient
1261
+ * jobs can be adopted by a Canvas after the controls object is created.
1262
+ */
1263
+ get rootId() {
1264
+ return scheduler.getScheduler().getJobRootId(id);
1265
+ },
1256
1266
  /**
1257
1267
  * Manually step this job only.
1258
1268
  * Bypasses FPS limiting - always runs.
@@ -1269,6 +1279,14 @@ function useFrame(callback, priorityOrOptions) {
1269
1279
  stepAll: (timestamp) => {
1270
1280
  scheduler.getScheduler().step(timestamp);
1271
1281
  },
1282
+ /**
1283
+ * Request frames for this job's owning root without waking sibling roots.
1284
+ */
1285
+ invalidate: (frames, stackFrames) => {
1286
+ const scheduler3 = scheduler.getScheduler();
1287
+ const rootId = scheduler3.getJobRootId(id);
1288
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1289
+ },
1272
1290
  /**
1273
1291
  * Pause this job (set enabled=false).
1274
1292
  * Job remains registered but won't run.
@@ -1341,12 +1359,24 @@ function useTexture(input, optionsOrOnLoad) {
1341
1359
  webgpu.TextureLoader,
1342
1360
  IsObject(stableInput) ? Object.values(stableInput) : stableInput
1343
1361
  );
1362
+ const mappedTextures = React.useMemo(() => {
1363
+ if (cachedResult) return cachedResult;
1364
+ if (IsObject(stableInput)) {
1365
+ const keyed = {};
1366
+ const textureArray = loadedTextures;
1367
+ let i = 0;
1368
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1369
+ return keyed;
1370
+ } else {
1371
+ return loadedTextures;
1372
+ }
1373
+ }, [stableInput, loadedTextures, cachedResult]);
1344
1374
  React.useLayoutEffect(() => {
1345
1375
  if (cachedResult) return;
1346
1376
  if (onLoadCalledForRef.current === inputKey) return;
1347
1377
  onLoadCalledForRef.current = inputKey;
1348
- onLoadRef.current?.(loadedTextures);
1349
- }, [cachedResult, loadedTextures, inputKey]);
1378
+ onLoadRef.current?.(mappedTextures);
1379
+ }, [cachedResult, mappedTextures, inputKey]);
1350
1380
  React.useEffect(() => {
1351
1381
  if (cachedResult) return;
1352
1382
  if ("initTexture" in renderer) {
@@ -1365,18 +1395,6 @@ function useTexture(input, optionsOrOnLoad) {
1365
1395
  });
1366
1396
  }
1367
1397
  }, [renderer, loadedTextures, cachedResult]);
1368
- const mappedTextures = React.useMemo(() => {
1369
- if (cachedResult) return cachedResult;
1370
- if (IsObject(stableInput)) {
1371
- const keyed = {};
1372
- const textureArray = loadedTextures;
1373
- let i = 0;
1374
- for (const key in stableInput) keyed[key] = textureArray[i++];
1375
- return keyed;
1376
- } else {
1377
- return loadedTextures;
1378
- }
1379
- }, [stableInput, loadedTextures, cachedResult]);
1380
1398
  React.useEffect(() => {
1381
1399
  if (!cache) return;
1382
1400
  const urlTextureMap = [];
@@ -1580,7 +1598,7 @@ function useEnvironment({
1580
1598
  texture = texture.renderTarget?.texture;
1581
1599
  }
1582
1600
  texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
1583
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1601
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1584
1602
  return texture;
1585
1603
  }
1586
1604
  const preloadDefaultOptions = {
@@ -1633,11 +1651,12 @@ function getExtension(files) {
1633
1651
  const isCubemap = isArray(files) && files.length === 6;
1634
1652
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1635
1653
  const firstEntry = isArray(files) ? files[0] : files;
1636
- 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();
1654
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1655
+ 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;
1637
1656
  return { extension, isCubemap, isGainmap };
1638
1657
  }
1639
1658
  function getLoader(extension) {
1640
- const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1659
+ const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader_js.HDRCubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1641
1660
  return loader;
1642
1661
  }
1643
1662
 
@@ -1688,13 +1707,19 @@ function addTail(callback) {
1688
1707
  return scheduler.getScheduler().onIdle(callback);
1689
1708
  }
1690
1709
  function invalidate(state, frames = 1, stackFrames = false) {
1691
- scheduler.getScheduler().invalidate(frames, stackFrames);
1710
+ const rootId = state?.internal?.rootId;
1711
+ const scheduler$1 = scheduler.getScheduler();
1712
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1713
+ else scheduler$1.invalidate(frames, stackFrames);
1692
1714
  }
1693
- function advance(timestamp) {
1694
- scheduler.getScheduler().step(timestamp);
1715
+ function advance(timestamp, runGlobalEffects, state) {
1716
+ const rootId = state?.internal?.rootId;
1717
+ const scheduler$1 = scheduler.getScheduler();
1718
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1719
+ else scheduler$1.step(timestamp);
1695
1720
  }
1696
1721
 
1697
- const version = "10.0.0-alpha.3";
1722
+ const version = "10.0.0-alpha.5";
1698
1723
  const packageData = {
1699
1724
  version: version};
1700
1725
 
@@ -13553,7 +13578,10 @@ function extend(objects) {
13553
13578
  catalogue[Component] = objects;
13554
13579
  return Component;
13555
13580
  } else {
13556
- Object.assign(catalogue, objects);
13581
+ for (const name in objects) {
13582
+ const object = objects[name];
13583
+ if (isConstructor(object)) catalogue[name] = object;
13584
+ }
13557
13585
  }
13558
13586
  }
13559
13587
  function validateInstance(type, props) {
@@ -13901,7 +13929,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13901
13929
  getRootHostContext: () => NO_CONTEXT,
13902
13930
  getChildHostContext: () => NO_CONTEXT,
13903
13931
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13904
- validateInstance(type, newProps);
13932
+ validateInstance(instance.type, newProps);
13905
13933
  let reconstruct = false;
13906
13934
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13907
13935
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13928,8 +13956,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13928
13956
  for (const prop in instance.props) {
13929
13957
  const value = instance.props[prop];
13930
13958
  if (isFromRef(value)) {
13931
- const ref = value[FROM_REF];
13932
- if (ref.current != null) resolved[prop] = ref.current;
13959
+ const { [FROM_REF]: ref, transform } = value;
13960
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13933
13961
  }
13934
13962
  }
13935
13963
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14228,9 +14256,8 @@ function createRoot(canvas) {
14228
14256
  if (!renderer.hasInitialized?.()) {
14229
14257
  const size2 = computeInitialSize(canvas, propsSize);
14230
14258
  if (size2.width > 0 && size2.height > 0) {
14231
- const pixelRatio = calculateDpr(dpr);
14232
- canvas.width = size2.width * pixelRatio;
14233
- canvas.height = size2.height * pixelRatio;
14259
+ renderer.setPixelRatio(calculateDpr(dpr));
14260
+ renderer.setSize(size2.width, size2.height, false);
14234
14261
  }
14235
14262
  await renderer.init();
14236
14263
  }
@@ -14239,7 +14266,7 @@ function createRoot(canvas) {
14239
14266
  state.internal.actualRenderer = renderer;
14240
14267
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14241
14268
  if (canvasId && !state.internal.isSecondary) {
14242
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14269
+ const canvasTarget = renderer.getCanvasTarget?.();
14243
14270
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14244
14271
  state.set((prev) => ({
14245
14272
  internal: {
@@ -14353,7 +14380,7 @@ function createRoot(canvas) {
14353
14380
  const handleXRFrame = (timestamp, _frame) => {
14354
14381
  const state2 = store.getState();
14355
14382
  if (state2.frameloop === "never") return;
14356
- advance(timestamp);
14383
+ advance(timestamp, true, state2);
14357
14384
  };
14358
14385
  const actualRenderer = state.internal.actualRenderer;
14359
14386
  const handleSessionChange = () => {
@@ -14361,7 +14388,7 @@ function createRoot(canvas) {
14361
14388
  const renderer2 = state2.internal.actualRenderer;
14362
14389
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14363
14390
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14364
- if (!renderer2.xr.isPresenting) invalidate();
14391
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14365
14392
  };
14366
14393
  const xr = {
14367
14394
  connect() {
@@ -14443,14 +14470,19 @@ function createRoot(canvas) {
14443
14470
  const newRootId = canvasId || scheduler$1.generateRootId();
14444
14471
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14445
14472
  getState: () => store.getState(),
14446
- 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
14447
14478
  });
14448
14479
  const unregisterCanvasTarget = scheduler$1.register(
14449
14480
  () => {
14450
14481
  const state2 = store.getState();
14451
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14482
+ const canvasTarget = state2.internal.canvasTarget;
14483
+ if (canvasTarget) {
14452
14484
  const renderer2 = state2.internal.actualRenderer;
14453
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14485
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14454
14486
  if (state2.internal.canvasTargetSizeDirty) {
14455
14487
  state2.internal.canvasTargetSizeDirty = false;
14456
14488
  renderer2.backend?.updateSize?.();
@@ -14523,8 +14555,7 @@ function createRoot(canvas) {
14523
14555
  phase: "render",
14524
14556
  system: true,
14525
14557
  // Internal flag: this is a system job, not user-controlled
14526
- // Apply scheduler config for render ordering and rate limiting
14527
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14558
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14528
14559
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14529
14560
  }
14530
14561
  );
@@ -14543,8 +14574,21 @@ function createRoot(canvas) {
14543
14574
  scheduler: scheduler$1
14544
14575
  }
14545
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
+ }
14546
14588
  }
14547
- scheduler$1.frameloop = frameloop;
14589
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14590
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14591
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14548
14592
  onCreated = onCreatedCallback;
14549
14593
  configured = true;
14550
14594
  resolve();
@@ -14586,18 +14630,23 @@ function unmountComponentAtNode(canvas, callback) {
14586
14630
  const fiber = root?.fiber;
14587
14631
  if (fiber) {
14588
14632
  const state = root?.store.getState();
14589
- 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
+ }
14590
14641
  reconciler.updateContainer(null, fiber, null, () => {
14591
14642
  if (state) {
14592
14643
  setTimeout(() => {
14593
14644
  try {
14594
14645
  const renderer = state.internal.actualRenderer;
14595
- const unregisterRoot = state.internal.unregisterRoot;
14596
- if (unregisterRoot) unregisterRoot();
14597
14646
  const unregisterPrimary = state.internal.unregisterPrimary;
14598
14647
  if (unregisterPrimary) unregisterPrimary();
14599
14648
  const canvasTarget = state.internal.canvasTarget;
14600
- if (canvasTarget?.dispose) canvasTarget.dispose();
14649
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14601
14650
  state.events.disconnect?.();
14602
14651
  cleanupHelperGroup(root.store);
14603
14652
  if (state.isLegacy && renderer) {
@@ -14738,16 +14787,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14738
14787
  const oldbg = target.background;
14739
14788
  const oldenv = target.environment;
14740
14789
  const oldSceneProps = {
14741
- // @ts-ignore
14742
14790
  backgroundBlurriness: target.backgroundBlurriness,
14743
- // @ts-ignore
14744
14791
  backgroundIntensity: target.backgroundIntensity,
14745
- // @ts-ignore
14746
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14747
- // @ts-ignore
14792
+ backgroundRotation: target.backgroundRotation.clone(),
14748
14793
  environmentIntensity: target.environmentIntensity,
14749
- // @ts-ignore
14750
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14794
+ environmentRotation: target.environmentRotation.clone()
14751
14795
  };
14752
14796
  if (background !== "only") target.environment = texture;
14753
14797
  if (background) target.background = texture;
@@ -15041,9 +15085,9 @@ function CanvasImpl({
15041
15085
  React__namespace.useMemo(() => extend(THREE), []);
15042
15086
  const Bridge = useBridge();
15043
15087
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15044
- const hasInitialSizeRef = React__namespace.useRef(false);
15088
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15045
15089
  const measureConfig = React__namespace.useMemo(() => {
15046
- if (!hasInitialSizeRef.current) {
15090
+ if (!hasInitialSize) {
15047
15091
  return {
15048
15092
  ...resize,
15049
15093
  scroll: resize?.scroll ?? true,
@@ -15055,7 +15099,7 @@ function CanvasImpl({
15055
15099
  debounce: { scroll: 50, resize: 0 },
15056
15100
  ...resize
15057
15101
  };
15058
- }, [resize, hasInitialSizeRef.current]);
15102
+ }, [hasInitialSize, resize]);
15059
15103
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15060
15104
  const effectiveSize = React__namespace.useMemo(() => {
15061
15105
  let w = width ?? containerRect.width;
@@ -15071,9 +15115,7 @@ function CanvasImpl({
15071
15115
  left: containerRect.left
15072
15116
  };
15073
15117
  }, [width, height, containerRect, forceEven]);
15074
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15075
- hasInitialSizeRef.current = true;
15076
- }
15118
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15077
15119
  const canvasRef = React__namespace.useRef(null);
15078
15120
  const divRef = React__namespace.useRef(null);
15079
15121
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15187,6 +15229,7 @@ function CanvasImpl({
15187
15229
  const canvas = canvasRef.current;
15188
15230
  if (canvas) {
15189
15231
  return () => {
15232
+ if (canvas.isConnected) return;
15190
15233
  unmountComponentAtNode(canvas);
15191
15234
  root.current = null;
15192
15235
  };