@react-three/fiber 8.2.2 → 9.0.0-alpha.0

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.
@@ -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 `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
+ var _stages;
1875
+
1876
+ const state = store.getState();
1877
+ let subscribers;
1878
+ let subscription;
1879
+ stages = (_stages = stages) != null ? _stages : Lifecycle;
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 createRoot(canvas) {
1704
1911
  // Check against mistaken use of createRoot
1705
1912
  let prevRoot = roots.get(canvas);
@@ -1741,7 +1948,8 @@ function createRoot(canvas) {
1741
1948
  performance,
1742
1949
  raycaster: raycastOptions,
1743
1950
  camera: cameraOptions,
1744
- onPointerMissed
1951
+ onPointerMissed,
1952
+ stages
1745
1953
  } = props;
1746
1954
  let state = store.getState(); // Set up renderer (one time only!)
1747
1955
 
@@ -1832,14 +2040,8 @@ function createRoot(canvas) {
1832
2040
  if (!isBoolean) Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
1833
2041
  if (old !== gl.shadowMap.enabled) gl.shadowMap.needsUpdate = true;
1834
2042
  }
1835
- } // Safely set color management if available.
1836
- // Avoid accessing THREE.ColorManagement to play nice with older versions
1837
-
1838
-
1839
- if ('ColorManagement' in THREE__namespace) {
1840
- setDeep(THREE__namespace, legacy, ['ColorManagement', 'legacyMode']);
1841
- }
1842
-
2043
+ } // Set color management
2044
+ THREE__namespace.ColorManagement.legacyMode = legacy;
1843
2045
  const outputEncoding = linear ? THREE__namespace.LinearEncoding : THREE__namespace.sRGBEncoding;
1844
2046
  const toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
1845
2047
  if (gl.outputEncoding !== outputEncoding) gl.outputEncoding = outputEncoding;
@@ -1890,7 +2092,9 @@ function createRoot(canvas) {
1890
2092
  performance: { ...state.performance,
1891
2093
  ...performance
1892
2094
  }
1893
- })); // Set locals
2095
+ })); // Create update stages.
2096
+
2097
+ if (stages !== state.internal.stages) createStages(stages, store); // Set locals
1894
2098
 
1895
2099
  onCreated = onCreatedCallback;
1896
2100
  configured = true;
@@ -2104,6 +2308,9 @@ const act = React__namespace.unstable_act;
2104
2308
 
2105
2309
  exports.Block = Block;
2106
2310
  exports.ErrorBoundary = ErrorBoundary;
2311
+ exports.FixedStage = FixedStage;
2312
+ exports.Stage = Stage;
2313
+ exports.Stages = Stages;
2107
2314
  exports.act = act;
2108
2315
  exports.addAfterEffect = addAfterEffect;
2109
2316
  exports.addEffect = addEffect;
@@ -2130,3 +2337,4 @@ exports.useLoader = useLoader;
2130
2337
  exports.useMutableCallback = useMutableCallback;
2131
2338
  exports.useStore = useStore;
2132
2339
  exports.useThree = useThree;
2340
+ exports.useUpdate = useUpdate;