@react-three/fiber 8.3.0 → 9.0.0-alpha.1

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.
@@ -1,9 +1,9 @@
1
- import * as React from 'react';
2
- import type { Options as ResizeOptions } from 'react-use-measure';
3
- import { RenderProps } from '../core';
4
- export interface Props extends Omit<RenderProps<HTMLCanvasElement>, 'size'>, React.HTMLAttributes<HTMLDivElement> {
5
- children: React.ReactNode;
6
- fallback?: React.ReactNode;
7
- resize?: ResizeOptions;
8
- }
9
- export declare const Canvas: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLCanvasElement>>;
1
+ import * as React from 'react';
2
+ import type { Options as ResizeOptions } from 'react-use-measure';
3
+ import { RenderProps } from '../core';
4
+ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size'>, React.HTMLAttributes<HTMLDivElement> {
5
+ children: React.ReactNode;
6
+ fallback?: React.ReactNode;
7
+ resize?: ResizeOptions;
8
+ }
9
+ export declare const Canvas: React.ForwardRefExoticComponent<CanvasProps & React.RefAttributes<HTMLCanvasElement>>;
@@ -1,4 +1,4 @@
1
- import { UseBoundStore } from 'zustand';
2
- import { RootState } from '../core/store';
3
- import { EventManager } from '../core/events';
4
- export declare function createPointerEvents(store: UseBoundStore<RootState>): EventManager<HTMLElement>;
1
+ import { UseBoundStore } from 'zustand';
2
+ import { RootState } from '../core/store';
3
+ import { EventManager } from '../core/events';
4
+ export declare function createPointerEvents(store: UseBoundStore<RootState>): EventManager<HTMLElement>;
@@ -365,12 +365,7 @@ function applyProps$1(instance, data) {
365
365
 
366
366
  if (!isColor && targetProp.setScalar) targetProp.setScalar(value); // Layers have no copy function, we must therefore copy the mask property
367
367
  else if (targetProp instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) targetProp.mask = value.mask; // Otherwise just set ...
368
- else targetProp.set(value); // For versions of three which don't support THREE.ColorManagement,
369
- // Auto-convert sRGB colors
370
- // https://github.com/pmndrs/react-three-fiber/issues/344
371
-
372
- const supportsColorManagement = ('ColorManagement' in THREE__namespace);
373
- if (!supportsColorManagement && !rootState.linear && isColor) targetProp.convertSRGBToLinear();
368
+ else targetProp.set(value);
374
369
  } // Else, just overwrite the value
375
370
 
376
371
  } else {
@@ -425,15 +420,6 @@ function updateCamera(camera, size) {
425
420
  camera.updateMatrixWorld();
426
421
  }
427
422
  }
428
- /**
429
- * Safely sets a deeply-nested value on an object.
430
- */
431
-
432
- function setDeep(obj, value, keys) {
433
- const key = keys.pop();
434
- const target = keys.reduce((acc, key) => acc[key], obj);
435
- return target[key] = value;
436
- }
437
423
 
438
424
  function makeId(event) {
439
425
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
@@ -1212,6 +1198,7 @@ function createRenderer(_roots, _getEventPriority) {
1212
1198
  };
1213
1199
  }
1214
1200
 
1201
+ // Keys that shouldn't be copied between R3F stores
1215
1202
  const privateKeys = ['set', 'get', 'setSize', 'setFrameloop', 'setDpr', 'events', 'invalidate', 'advance', 'size', 'viewport'];
1216
1203
  const isRenderer = def => !!(def != null && def.render);
1217
1204
  const context = /*#__PURE__*/React__namespace.createContext(null);
@@ -1359,8 +1346,14 @@ const createStore = (invalidate, advance) => {
1359
1346
  }
1360
1347
  };
1361
1348
  }),
1362
- setFrameloop: (frameloop = 'always') => {
1363
- const clock = get().clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1349
+ setFrameloop: frameloop => {
1350
+ var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
1351
+
1352
+ const state = get();
1353
+ const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
1354
+ const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
1355
+ const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
1356
+ const clock = state.clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1364
1357
 
1365
1358
  clock.stop();
1366
1359
  clock.elapsedTime = 0;
@@ -1371,28 +1364,44 @@ const createStore = (invalidate, advance) => {
1371
1364
  }
1372
1365
 
1373
1366
  set(() => ({
1374
- frameloop
1367
+ frameloop: mode,
1368
+ internal: { ...state.internal,
1369
+ render,
1370
+ maxDelta
1371
+ }
1375
1372
  }));
1376
1373
  },
1377
1374
  previousRoot: undefined,
1378
1375
  internal: {
1379
- active: false,
1380
- priority: 0,
1381
- frames: 0,
1382
- lastEvent: /*#__PURE__*/React__namespace.createRef(),
1376
+ // Events
1383
1377
  interaction: [],
1384
1378
  hovered: new Map(),
1385
1379
  subscribers: [],
1386
1380
  initialClick: [0, 0],
1387
1381
  initialHits: [],
1388
1382
  capturedMap: new Map(),
1383
+ lastEvent: /*#__PURE__*/React__namespace.createRef(),
1384
+ // Updates
1385
+ active: false,
1386
+ frames: 0,
1387
+ stages: [],
1388
+ render: 'auto',
1389
+ maxDelta: 1 / 10,
1390
+ priority: 0,
1389
1391
  subscribe: (ref, priority, store) => {
1390
- const internal = get().internal; // If this subscription was given a priority, it takes rendering into its own hands
1392
+ const state = get();
1393
+ const internal = state.internal; // If this subscription was given a priority, it takes rendering into its own hands
1391
1394
  // For that reason we switch off automatic rendering and increase the manual flag
1392
1395
  // As long as this flag is positive there can be no internal rendering at all
1393
1396
  // because there could be multiple render subscriptions
1394
1397
 
1395
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1398
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
1399
+
1400
+ if (internal.priority && state.internal.render === 'auto') set(() => ({
1401
+ internal: { ...state.internal,
1402
+ render: 'manual'
1403
+ }
1404
+ }));
1396
1405
  internal.subscribers.push({
1397
1406
  ref,
1398
1407
  priority,
@@ -1402,11 +1411,18 @@ const createStore = (invalidate, advance) => {
1402
1411
 
1403
1412
  internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1404
1413
  return () => {
1405
- const internal = get().internal;
1414
+ const state = get();
1415
+ const internal = state.internal;
1406
1416
 
1407
1417
  if (internal != null && internal.subscribers) {
1408
1418
  // Decrease manual flag if this subscription had a priority
1409
- internal.priority = internal.priority - (priority > 0 ? 1 : 0); // Remove subscriber from list
1419
+ internal.priority = internal.priority - (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
1420
+
1421
+ if (!internal.priority && state.internal.render === 'manual') set(() => ({
1422
+ internal: { ...state.internal,
1423
+ render: 'auto'
1424
+ }
1425
+ })); // Remove subscriber from list
1410
1426
 
1411
1427
  internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
1412
1428
  }
@@ -1462,8 +1478,6 @@ function createSubs(callback, subs) {
1462
1478
  subs.add(sub);
1463
1479
  return () => void subs.delete(sub);
1464
1480
  }
1465
-
1466
- let i;
1467
1481
  let globalEffects = new Set();
1468
1482
  let globalAfterEffects = new Set();
1469
1483
  let globalTailEffects = new Set();
@@ -1492,10 +1506,7 @@ function run(effects, timestamp) {
1492
1506
  }) => callback(timestamp));
1493
1507
  }
1494
1508
 
1495
- let subscribers;
1496
- let subscription;
1497
-
1498
- function render$1(timestamp, state, frame) {
1509
+ function update(timestamp, state, frame) {
1499
1510
  // Run local effects
1500
1511
  let delta = state.clock.getDelta(); // In frameloop='never' mode, clock times are updated using the provided timestamp
1501
1512
 
@@ -1503,18 +1514,14 @@ function render$1(timestamp, state, frame) {
1503
1514
  delta = timestamp - state.clock.elapsedTime;
1504
1515
  state.clock.oldTime = state.clock.elapsedTime;
1505
1516
  state.clock.elapsedTime = timestamp;
1506
- } // Call subscribers (useFrame)
1517
+ } else {
1518
+ delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
1519
+ } // Call subscribers (useUpdate)
1507
1520
 
1508
1521
 
1509
- subscribers = state.internal.subscribers;
1510
-
1511
- for (i = 0; i < subscribers.length; i++) {
1512
- subscription = subscribers[i];
1513
- subscription.ref.current(subscription.store.getState(), delta, frame);
1514
- } // Render content
1515
-
1516
-
1517
- if (!state.internal.priority && state.gl.render) state.gl.render(state.scene, state.camera); // Decrease frame count
1522
+ for (const stage of state.internal.stages) {
1523
+ stage.frame(delta, frame);
1524
+ }
1518
1525
 
1519
1526
  state.internal.frames = Math.max(0, state.internal.frames - 1);
1520
1527
  return state.frameloop === 'always' ? 1 : state.internal.frames;
@@ -1539,7 +1546,7 @@ function createLoop(roots) {
1539
1546
  state = root.store.getState(); // If the frameloop is invalidated, do not run another frame
1540
1547
 
1541
1548
  if (state.internal.active && (state.frameloop === 'always' || state.internal.frames > 0) && !((_state$gl$xr = state.gl.xr) != null && _state$gl$xr.isPresenting)) {
1542
- repeat += render$1(timestamp, state);
1549
+ repeat += update(timestamp, state);
1543
1550
  }
1544
1551
  }); // Run after-effects
1545
1552
 
@@ -1570,7 +1577,7 @@ function createLoop(roots) {
1570
1577
 
1571
1578
  function advance(timestamp, runGlobalEffects = true, state, frame) {
1572
1579
  if (runGlobalEffects) run(globalEffects, timestamp);
1573
- if (!state) roots.forEach(root => render$1(timestamp, root.store.getState()));else render$1(timestamp, state, frame);
1580
+ if (!state) roots.forEach(root => update(timestamp, root.store.getState()));else update(timestamp, state, frame);
1574
1581
  if (runGlobalEffects) run(globalAfterEffects, timestamp);
1575
1582
  }
1576
1583
 
@@ -1591,6 +1598,154 @@ function createLoop(roots) {
1591
1598
  };
1592
1599
  }
1593
1600
 
1601
+ /**
1602
+ * Class representing a stage that updates every frame.
1603
+ * Stages are used to build a lifecycle of effects for an app's frameloop.
1604
+ */
1605
+ class Stage {
1606
+ constructor() {
1607
+ this.subscribers = [];
1608
+ this._frameTime = 0;
1609
+ }
1610
+ /**
1611
+ * Executes all callback subscriptions on the stage.
1612
+ * @param delta - Delta time between frame calls.
1613
+ * @param [frame] - The XR frame if it exists.
1614
+ */
1615
+
1616
+
1617
+ frame(delta, frame) {
1618
+ const subs = this.subscribers;
1619
+ const initialTime = performance.now();
1620
+
1621
+ for (let i = 0; i < subs.length; i++) {
1622
+ subs[i].ref.current(subs[i].store.getState(), delta, frame);
1623
+ }
1624
+
1625
+ this._frameTime = performance.now() - initialTime;
1626
+ }
1627
+ /**
1628
+ * Adds a callback subscriber to the stage.
1629
+ * @param ref - The mutable callback reference.
1630
+ * @param store - The store to be used with the callback execution.
1631
+ * @returns A function to remove the subscription.
1632
+ */
1633
+
1634
+
1635
+ add(ref, store) {
1636
+ this.subscribers.push({
1637
+ ref,
1638
+ store
1639
+ });
1640
+ return () => {
1641
+ this.subscribers = this.subscribers.filter(sub => {
1642
+ return sub.ref !== ref;
1643
+ });
1644
+ };
1645
+ }
1646
+
1647
+ get frameTime() {
1648
+ return this._frameTime;
1649
+ }
1650
+
1651
+ } // Using Unity's fixedStep default.
1652
+
1653
+ const FPS_50 = 1 / 50;
1654
+ /**
1655
+ * Class representing a stage that updates every frame at a fixed rate.
1656
+ * @param name - Name of the stage.
1657
+ * @param [fixedStep] - Fixed step rate.
1658
+ * @param [maxSubsteps] - Maximum number of substeps.
1659
+ */
1660
+
1661
+ class FixedStage extends Stage {
1662
+ constructor(fixedStep, maxSubSteps) {
1663
+ super();
1664
+ this._fixedStep = fixedStep != null ? fixedStep : FPS_50;
1665
+ this._maxSubsteps = maxSubSteps != null ? maxSubSteps : 6;
1666
+ this._accumulator = 0;
1667
+ this._alpha = 0;
1668
+ this._fixedFrameTime = 0;
1669
+ this._substepTimes = [];
1670
+ }
1671
+ /**
1672
+ * Executes all callback subscriptions on the stage.
1673
+ * @param delta - Delta time between frame calls.
1674
+ * @param [frame] - The XR frame if it exists.
1675
+ */
1676
+
1677
+
1678
+ frame(delta, frame) {
1679
+ const initialTime = performance.now();
1680
+ let substeps = 0;
1681
+ this._substepTimes = [];
1682
+ this._accumulator += delta;
1683
+
1684
+ while (this._accumulator >= this._fixedStep && substeps < this._maxSubsteps) {
1685
+ this._accumulator -= this._fixedStep;
1686
+ substeps++;
1687
+ super.frame(this._fixedStep, frame);
1688
+
1689
+ this._substepTimes.push(super.frameTime);
1690
+ }
1691
+
1692
+ this._fixedFrameTime = performance.now() - initialTime; // The accumulator will only be larger than the fixed step if we had to
1693
+ // bail early due to hitting the max substep limit or execution time lagging.
1694
+ // In that case, we want to shave off the excess so we don't fall behind next frame.
1695
+
1696
+ this._accumulator = this._accumulator % this._fixedStep;
1697
+ this._alpha = this._accumulator / this._fixedStep;
1698
+ }
1699
+
1700
+ get frameTime() {
1701
+ return this._fixedFrameTime;
1702
+ }
1703
+
1704
+ get substepTimes() {
1705
+ return this._substepTimes;
1706
+ }
1707
+
1708
+ get fixedStep() {
1709
+ return this._fixedStep;
1710
+ }
1711
+
1712
+ set fixedStep(fixedStep) {
1713
+ this._fixedStep = fixedStep;
1714
+ }
1715
+
1716
+ get maxSubsteps() {
1717
+ return this._maxSubsteps;
1718
+ }
1719
+
1720
+ set maxSubsteps(maxSubsteps) {
1721
+ this._maxSubsteps = maxSubsteps;
1722
+ }
1723
+
1724
+ get accumulator() {
1725
+ return this._accumulator;
1726
+ }
1727
+
1728
+ get alpha() {
1729
+ return this._alpha;
1730
+ }
1731
+
1732
+ }
1733
+ const Early = new Stage();
1734
+ const Fixed = new FixedStage();
1735
+ const Update = new Stage();
1736
+ const Late = new Stage();
1737
+ const Render = new Stage();
1738
+ const After = new Stage();
1739
+ const Stages = {
1740
+ Early,
1741
+ Fixed,
1742
+ Update,
1743
+ Late,
1744
+ Render,
1745
+ After
1746
+ };
1747
+ const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1748
+
1594
1749
  function useStore() {
1595
1750
  const store = React__namespace.useContext(context);
1596
1751
  if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
@@ -1619,6 +1774,21 @@ function useFrame(callback, renderPriority = 0) {
1619
1774
  useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1620
1775
  return null;
1621
1776
  }
1777
+ /**
1778
+ * Executes a callback in a given update stage.
1779
+ * Uses the stage instance to indetify which stage to target in the lifecycle.
1780
+ */
1781
+
1782
+ function useUpdate(callback, stage = Stages.Update) {
1783
+ const store = useStore();
1784
+ const stages = store.getState().internal.stages; // Memoize ref
1785
+
1786
+ const ref = useMutableCallback(callback); // Throw an error if a stage does not exist in the lifecycle
1787
+
1788
+ if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`); // Subscribe on mount, unsubscribe on unmount
1789
+
1790
+ useIsomorphicLayoutEffect(() => stage.add(ref, store), [stage]);
1791
+ }
1622
1792
  /**
1623
1793
  * Returns a node graph of an object with named nodes & materials.
1624
1794
  * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
@@ -1700,6 +1870,43 @@ const createRendererInstance = (gl, canvas) => {
1700
1870
  });
1701
1871
  };
1702
1872
 
1873
+ const createStages = (stages, store) => {
1874
+ const state = store.getState();
1875
+ let subscribers;
1876
+ let subscription;
1877
+
1878
+ const _stages = stages != null ? stages : Lifecycle;
1879
+
1880
+ if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1881
+ if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1882
+ state.set(({
1883
+ internal
1884
+ }) => ({
1885
+ internal: { ...internal,
1886
+ stages: _stages
1887
+ }
1888
+ })); // Add useFrame loop to update stage
1889
+
1890
+ const frameCallback = {
1891
+ current: (state, delta, frame) => {
1892
+ subscribers = state.internal.subscribers;
1893
+
1894
+ for (let i = 0; i < subscribers.length; i++) {
1895
+ subscription = subscribers[i];
1896
+ subscription.ref.current(subscription.store.getState(), delta, frame);
1897
+ }
1898
+ }
1899
+ };
1900
+ Stages.Update.add(frameCallback, store); // Add render callback to render stage
1901
+
1902
+ const renderCallback = {
1903
+ current: state => {
1904
+ if (state.internal.render === 'auto' && state.gl.render) state.gl.render(state.scene, state.camera);
1905
+ }
1906
+ };
1907
+ Stages.Render.add(renderCallback, store);
1908
+ };
1909
+
1703
1910
  function isCanvas(maybeCanvas) {
1704
1911
  return maybeCanvas instanceof HTMLCanvasElement;
1705
1912
  }
@@ -1773,7 +1980,8 @@ function createRoot(canvas) {
1773
1980
  performance,
1774
1981
  raycaster: raycastOptions,
1775
1982
  camera: cameraOptions,
1776
- onPointerMissed
1983
+ onPointerMissed,
1984
+ stages
1777
1985
  } = props;
1778
1986
  let state = store.getState(); // Set up renderer (one time only!)
1779
1987
 
@@ -1864,14 +2072,8 @@ function createRoot(canvas) {
1864
2072
  if (!isBoolean) Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
1865
2073
  if (old !== gl.shadowMap.enabled) gl.shadowMap.needsUpdate = true;
1866
2074
  }
1867
- } // Safely set color management if available.
1868
- // Avoid accessing THREE.ColorManagement to play nice with older versions
1869
-
1870
-
1871
- if ('ColorManagement' in THREE__namespace) {
1872
- setDeep(THREE__namespace, legacy, ['ColorManagement', 'legacyMode']);
1873
- }
1874
-
2075
+ } // Set color management
2076
+ THREE__namespace.ColorManagement.legacyMode = legacy;
1875
2077
  const outputEncoding = linear ? THREE__namespace.LinearEncoding : THREE__namespace.sRGBEncoding;
1876
2078
  const toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
1877
2079
  if (gl.outputEncoding !== outputEncoding) gl.outputEncoding = outputEncoding;
@@ -1912,7 +2114,9 @@ function createRoot(canvas) {
1912
2114
  performance: { ...state.performance,
1913
2115
  ...performance
1914
2116
  }
1915
- })); // Set locals
2117
+ })); // Create update stages. Only do this once on init
2118
+
2119
+ if (state.internal.stages.length === 0) createStages(stages, store); // Set locals
1916
2120
 
1917
2121
  onCreated = onCreatedCallback;
1918
2122
  configured = true;
@@ -2126,6 +2330,9 @@ const act = React__namespace.unstable_act;
2126
2330
 
2127
2331
  exports.Block = Block;
2128
2332
  exports.ErrorBoundary = ErrorBoundary;
2333
+ exports.FixedStage = FixedStage;
2334
+ exports.Stage = Stage;
2335
+ exports.Stages = Stages;
2129
2336
  exports.act = act;
2130
2337
  exports.addAfterEffect = addAfterEffect;
2131
2338
  exports.addEffect = addEffect;
@@ -2152,3 +2359,4 @@ exports.useLoader = useLoader;
2152
2359
  exports.useMutableCallback = useMutableCallback;
2153
2360
  exports.useStore = useStore;
2154
2361
  exports.useThree = useThree;
2362
+ exports.useUpdate = useUpdate;