@react-three/fiber 9.0.0-alpha.7 → 9.0.0-alpha.8

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.
@@ -38,6 +38,9 @@ var threeTypes = /*#__PURE__*/Object.freeze({
38
38
  __proto__: null
39
39
  });
40
40
 
41
+ // TODO: upstream to DefinitelyTyped for React 19
42
+ // https://github.com/facebook/react/issues/28956
43
+
41
44
  const createReconciler = Reconciler__default["default"];
42
45
 
43
46
  // TODO: handle constructor overloads
@@ -46,25 +49,17 @@ const createReconciler = Reconciler__default["default"];
46
49
 
47
50
  const catalogue = {};
48
51
  let i = 0;
49
- const extend = objects => {
50
- if (typeof objects === 'function') {
52
+ const isConstructor$1 = object => typeof object === 'function';
53
+ function extend(objects) {
54
+ if (isConstructor$1(objects)) {
51
55
  const Component = `${i++}`;
52
56
  catalogue[Component] = objects;
53
-
54
- // Returns a component whose name will be inferred in devtools
55
- // @ts-expect-error
56
- return /*#__PURE__*/React__namespace.forwardRef({
57
- [objects.name]: (props, ref) => /*#__PURE__*/jsxRuntime.jsx(Component, {
58
- ...props,
59
- ref: ref
60
- })
61
- }[objects.name]);
57
+ return Component;
62
58
  } else {
63
- return void Object.assign(catalogue, objects);
59
+ Object.assign(catalogue, objects);
64
60
  }
65
- };
66
- function createInstance(type, props, root, flushPrimitive = true) {
67
- var _props$object;
61
+ }
62
+ function validateInstance(type, props) {
68
63
  // Get target from catalogue
69
64
  const name = `${type[0].toUpperCase()}${type.slice(1)}`;
70
65
  const target = catalogue[name];
@@ -77,13 +72,14 @@ function createInstance(type, props, root, flushPrimitive = true) {
77
72
 
78
73
  // Throw if an object or literal was passed for args
79
74
  if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
75
+ }
76
+ function createInstance(type, props, root) {
77
+ var _props$object;
78
+ validateInstance(type, props);
80
79
 
81
80
  // Regenerate the R3F instance for primitives to simulate a new object
82
- if (flushPrimitive && type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
83
-
84
- // Create instance
85
- const instance = prepare(props.object, root, type, props);
86
- return instance;
81
+ if (type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
82
+ return prepare(props.object, root, type, props);
87
83
  }
88
84
  function hideInstance(instance) {
89
85
  if (!instance.isHidden) {
@@ -180,15 +176,29 @@ function insertBefore(parent, child, beforeChild) {
180
176
  // Attach tree once complete
181
177
  handleContainerEffects(parent, child, beforeChild);
182
178
  }
183
- function removeChild(parent, child, dispose, recursive) {
179
+ function disposeOnIdle(object) {
180
+ if (typeof object.dispose === 'function') {
181
+ const handleDispose = () => {
182
+ try {
183
+ object.dispose();
184
+ } catch {
185
+ // no-op
186
+ }
187
+ };
188
+
189
+ // In a testing environment, cleanup immediately
190
+ if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
191
+ // Otherwise, using a real GPU so schedule cleanup to prevent stalls
192
+ else scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, handleDispose);
193
+ }
194
+ }
195
+ function removeChild(parent, child, dispose) {
184
196
  if (!child) return;
185
197
 
186
198
  // Unlink instances
187
199
  child.parent = null;
188
- if (recursive === undefined) {
189
- const childIndex = parent.children.indexOf(child);
190
- if (childIndex !== -1) parent.children.splice(childIndex, 1);
191
- }
200
+ const childIndex = parent.children.indexOf(child);
201
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
192
202
 
193
203
  // Eagerly tear down tree
194
204
  if (child.props.attach) {
@@ -202,10 +212,11 @@ function removeChild(parent, child, dispose, recursive) {
202
212
  const shouldDispose = child.props.dispose !== null && dispose !== false;
203
213
 
204
214
  // Recursively remove instance children
205
- if (recursive !== false) {
206
- for (const node of child.children) removeChild(child, node, shouldDispose, true);
207
- child.children.length = 0;
215
+ for (let i = child.children.length - 1; i >= 0; i--) {
216
+ const node = child.children[i];
217
+ removeChild(child, node, shouldDispose);
208
218
  }
219
+ child.children.length = 0;
209
220
 
210
221
  // Unlink instance object
211
222
  delete child.object.__r3f;
@@ -217,96 +228,108 @@ function removeChild(parent, child, dispose, recursive) {
217
228
  // - cannot be a <primitive object={...} />
218
229
  // - cannot be a THREE.Scene, because three has broken its own API
219
230
  if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
220
- if (typeof child.object.dispose === 'function') {
221
- const dispose = child.object.dispose.bind(child.object);
222
- const handleDispose = () => {
223
- try {
224
- dispose();
225
- } catch (e) {
226
- // no-op
227
- }
228
- };
229
-
230
- // In a testing environment, cleanup immediately
231
- if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
232
- // Otherwise, using a real GPU so schedule cleanup to prevent stalls
233
- else scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, handleDispose);
234
- }
231
+ disposeOnIdle(child.object);
235
232
  }
236
233
 
237
234
  // Tree was updated, request a frame for top-level instance
238
235
  if (dispose === undefined) invalidateInstance(child);
239
236
  }
240
- function setFiberInstance(fiber, instance) {
241
- if (fiber !== null) {
242
- fiber.stateNode = instance;
243
- if (typeof fiber.ref === 'function') fiber.ref(instance.object);else if (fiber.ref) fiber.ref.current = instance.object;
237
+ function setFiberRef(fiber, publicInstance) {
238
+ for (const _fiber of [fiber, fiber.alternate]) {
239
+ if (_fiber !== null) {
240
+ if (typeof _fiber.ref === 'function') {
241
+ _fiber.refCleanup == null ? void 0 : _fiber.refCleanup();
242
+ const cleanup = _fiber.ref(publicInstance);
243
+ if (typeof cleanup === 'function') _fiber.refCleanup = cleanup;
244
+ } else if (_fiber.ref) {
245
+ _fiber.ref.current = publicInstance;
246
+ }
247
+ }
244
248
  }
245
249
  }
246
- function switchInstance(oldInstance, type, props, fiber) {
247
- // If the old instance is hidden, we need to unhide it.
248
- // React assumes it can discard instances since they're pure for DOM.
249
- // This isn't true for us since our lifetimes are impure and longliving.
250
- // So, we manually check if an instance was hidden and unhide it.
251
- if (oldInstance.isHidden) unhideInstance(oldInstance);
252
-
253
- // Create a new instance
254
- const newInstance = createInstance(type, props, oldInstance.root, false);
255
-
256
- // Update attach props for primitives since we don't flush them
257
- if (type === 'primitive') {
258
- newInstance.props.attach = props.attach;
259
- }
250
+ const reconstructed = [];
251
+ function swapInstances() {
252
+ // Detach instance
253
+ for (const [instance] of reconstructed) {
254
+ const parent = instance.parent;
255
+ if (parent) {
256
+ if (instance.props.attach) {
257
+ detach(parent, instance);
258
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
259
+ parent.object.remove(instance.object);
260
+ }
261
+ for (const child of instance.children) {
262
+ if (child.props.attach) {
263
+ detach(instance, child);
264
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
265
+ instance.object.remove(child.object);
266
+ }
267
+ }
268
+ }
260
269
 
261
- // Move children to new instance
262
- for (const child of oldInstance.children) {
263
- removeChild(oldInstance, child, false, false);
264
- appendChild(newInstance, child);
265
- }
266
- oldInstance.children.length = 0;
267
-
268
- // Link up new instance
269
- const parent = oldInstance.parent;
270
- if (parent) {
271
- // Manually handle replace https://github.com/pmndrs/react-three-fiber/pull/2680
272
-
273
- newInstance.autoRemovedBeforeAppend = !!newInstance.parent;
274
- if (!oldInstance.autoRemovedBeforeAppend) removeChild(parent, oldInstance);
275
- appendChild(parent, newInstance);
276
-
277
- // if (!oldInstance.autoRemovedBeforeAppend) {
278
- // insertBefore(parent, newInstance, oldInstance)
279
- // removeChild(parent, oldInstance)
280
- // } else {
281
- // appendChild(parent, newInstance)
282
- // }
270
+ // If the old instance is hidden, we need to unhide it.
271
+ // React assumes it can discard instances since they're pure for DOM.
272
+ // This isn't true for us since our lifetimes are impure and longliving.
273
+ // So, we manually check if an instance was hidden and unhide it.
274
+ if (instance.isHidden) unhideInstance(instance);
275
+
276
+ // Dispose of old object if able
277
+ if (instance.object.__r3f) delete instance.object.__r3f;
278
+ if (instance.type !== 'primitive') disposeOnIdle(instance.object);
283
279
  }
284
280
 
285
- // This evil hack switches the react-internal fiber instance
286
- // https://github.com/facebook/react/issues/14983
287
- // TODO: investigate scheduling key prop change instead of switchInstance entirely
288
- // https://github.com/facebook/react/pull/15021#issuecomment-480185369
289
- setFiberInstance(fiber, newInstance);
290
- setFiberInstance(fiber.alternate, newInstance);
281
+ // Update instance
282
+ for (const [instance, props, fiber] of reconstructed) {
283
+ instance.props = props;
284
+ const parent = instance.parent;
285
+ if (parent) {
286
+ var _instance$props$objec, _instance$props$args;
287
+ // Get target from catalogue
288
+ const name = `${instance.type[0].toUpperCase()}${instance.type.slice(1)}`;
289
+ const target = catalogue[name];
290
+
291
+ // Create object
292
+ instance.object = (_instance$props$objec = instance.props.object) != null ? _instance$props$objec : new target(...((_instance$props$args = instance.props.args) != null ? _instance$props$args : []));
293
+ instance.object.__r3f = instance;
294
+ setFiberRef(fiber, instance.object);
295
+
296
+ // Set initial props
297
+ applyProps(instance.object, instance.props);
298
+ if (instance.props.attach) {
299
+ attach(parent, instance);
300
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
301
+ parent.object.add(instance.object);
302
+ }
303
+ for (const child of instance.children) {
304
+ if (child.props.attach) {
305
+ attach(instance, child);
306
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
307
+ instance.object.add(child.object);
308
+ }
309
+ }
291
310
 
292
- // Tree was updated, request a frame
293
- invalidateInstance(newInstance);
294
- return newInstance;
311
+ // Tree was updated, request a frame
312
+ invalidateInstance(instance);
313
+ }
314
+ }
315
+ reconstructed.length = 0;
295
316
  }
296
317
 
297
318
  // Don't handle text instances, warn on undefined behavior
298
319
  const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
299
320
  const NO_CONTEXT = {};
300
321
  let currentUpdatePriority = constants.NoEventPriority;
322
+
323
+ // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
324
+ const NoFlags = 0;
325
+ const Update = 4;
301
326
  const reconciler = createReconciler({
302
327
  isPrimaryRenderer: false,
303
328
  warnsIfNotActing: false,
304
329
  supportsMutation: true,
305
330
  supportsPersistence: false,
306
331
  supportsHydration: false,
307
- createInstance(type, props, root) {
308
- return createInstance(type, props, root);
309
- },
332
+ createInstance,
310
333
  removeChild,
311
334
  appendChild,
312
335
  appendInitialChild: appendChild,
@@ -330,12 +353,11 @@ const reconciler = createReconciler({
330
353
  getChildHostContext: () => NO_CONTEXT,
331
354
  commitUpdate(instance, type, oldProps, newProps, fiber) {
332
355
  var _newProps$args, _oldProps$args, _newProps$args2;
356
+ validateInstance(type, newProps);
333
357
  let reconstruct = false;
334
358
 
335
359
  // Reconstruct primitives if object prop changes
336
360
  if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
337
- // Reconstruct instance if args was changed to an invalid value
338
- else if (newProps.args !== undefined && !Array.isArray(newProps.args)) reconstruct = true;
339
361
  // Reconstruct instance if args were added or removed
340
362
  else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
341
363
  // Reconstruct instance if args were changed
@@ -345,14 +367,22 @@ const reconciler = createReconciler({
345
367
  })) reconstruct = true;
346
368
 
347
369
  // Reconstruct when args or <primitive object={...} have changes
348
- if (reconstruct) return switchInstance(instance, type, newProps, fiber);
349
-
350
- // Create a diff-set, flag if there are any changes
351
- const changedProps = diffProps(instance, newProps);
352
- if (Object.keys(changedProps).length) {
353
- Object.assign(instance.props, changedProps);
354
- applyProps(instance.object, changedProps);
370
+ if (reconstruct) {
371
+ reconstructed.push([instance, {
372
+ ...newProps
373
+ }, fiber]);
374
+ } else {
375
+ // Create a diff-set, flag if there are any changes
376
+ const changedProps = diffProps(instance, newProps);
377
+ if (Object.keys(changedProps).length) {
378
+ Object.assign(instance.props, changedProps);
379
+ applyProps(instance.object, changedProps);
380
+ }
355
381
  }
382
+
383
+ // Flush reconstructed siblings when we hit the last updated child in a sequence
384
+ const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
385
+ if (isTailSibling) swapInstances();
356
386
  },
357
387
  finalizeInitialChildren: () => false,
358
388
  commitMount() {},
@@ -697,7 +727,7 @@ function detach(parent, child) {
697
727
  }
698
728
  const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
699
729
  // Instance props
700
- 'args', 'dispose', 'attach', 'object',
730
+ 'args', 'dispose', 'attach', 'object', 'onUpdate',
701
731
  // Behavior flags
702
732
  'dispose'];
703
733
  const MEMOIZED_PROTOTYPES = new Map();
@@ -871,6 +901,8 @@ function applyProps(object, props) {
871
901
  }
872
902
  function invalidateInstance(instance) {
873
903
  var _instance$root;
904
+ if (!instance.parent) return;
905
+ instance.props.onUpdate == null ? void 0 : instance.props.onUpdate(instance.object);
874
906
  const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
875
907
  if (state && state.internal.frames === 0) state.invalidate();
876
908
  }
@@ -1433,13 +1465,9 @@ const createStore = (invalidate, advance) => {
1433
1465
  }
1434
1466
  };
1435
1467
  }),
1436
- setFrameloop: frameloop => {
1437
- var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
1438
- const state = get();
1439
- 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;
1440
- const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
1441
- const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
1442
- const clock = state.clock;
1468
+ setFrameloop: (frameloop = 'always') => {
1469
+ const clock = get().clock;
1470
+
1443
1471
  // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1444
1472
  clock.stop();
1445
1473
  clock.elapsedTime = 0;
@@ -1448,12 +1476,7 @@ const createStore = (invalidate, advance) => {
1448
1476
  clock.elapsedTime = 0;
1449
1477
  }
1450
1478
  set(() => ({
1451
- frameloop: mode,
1452
- internal: {
1453
- ...state.internal,
1454
- render,
1455
- maxDelta
1456
- }
1479
+ frameloop
1457
1480
  }));
1458
1481
  },
1459
1482
  previousRoot: undefined,
@@ -1469,25 +1492,14 @@ const createStore = (invalidate, advance) => {
1469
1492
  // Updates
1470
1493
  active: false,
1471
1494
  frames: 0,
1472
- stages: [],
1473
- render: 'auto',
1474
- maxDelta: 1 / 10,
1475
1495
  priority: 0,
1476
1496
  subscribe: (ref, priority, store) => {
1477
- const state = get();
1478
- const internal = state.internal;
1497
+ const internal = get().internal;
1479
1498
  // If this subscription was given a priority, it takes rendering into its own hands
1480
1499
  // For that reason we switch off automatic rendering and increase the manual flag
1481
1500
  // As long as this flag is positive there can be no internal rendering at all
1482
1501
  // because there could be multiple render subscriptions
1483
1502
  internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1484
- // We use the render flag and deprecate priority
1485
- if (internal.priority && state.internal.render === 'auto') set(() => ({
1486
- internal: {
1487
- ...state.internal,
1488
- render: 'manual'
1489
- }
1490
- }));
1491
1503
  internal.subscribers.push({
1492
1504
  ref,
1493
1505
  priority,
@@ -1497,18 +1509,10 @@ const createStore = (invalidate, advance) => {
1497
1509
  // highest priority renders last (on top of the other frames)
1498
1510
  internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1499
1511
  return () => {
1500
- const state = get();
1501
- const internal = state.internal;
1512
+ const internal = get().internal;
1502
1513
  if (internal != null && internal.subscribers) {
1503
1514
  // Decrease manual flag if this subscription had a priority
1504
1515
  internal.priority = internal.priority - (priority > 0 ? 1 : 0);
1505
- // We use the render flag and deprecate priority
1506
- if (!internal.priority && state.internal.render === 'manual') set(() => ({
1507
- internal: {
1508
- ...state.internal,
1509
- render: 'auto'
1510
- }
1511
- }));
1512
1516
  // Remove subscriber from list
1513
1517
  internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
1514
1518
  }
@@ -1562,139 +1566,6 @@ const createStore = (invalidate, advance) => {
1562
1566
  return rootStore;
1563
1567
  };
1564
1568
 
1565
- // TODO: Remove deprecated fields in `Subscription`
1566
-
1567
- /**
1568
- * Class representing a stage that updates every frame.
1569
- * Stages are used to build a lifecycle of effects for an app's frameloop.
1570
- */
1571
- class Stage {
1572
- constructor() {
1573
- this.subscribers = [];
1574
- this._frameTime = 0;
1575
- }
1576
-
1577
- /**
1578
- * Executes all callback subscriptions on the stage.
1579
- * @param delta - Delta time between frame calls.
1580
- * @param [frame] - The XR frame if it exists.
1581
- */
1582
- frame(delta, frame) {
1583
- const subs = this.subscribers;
1584
- const initialTime = performance.now();
1585
- for (let i = 0; i < subs.length; i++) {
1586
- subs[i].ref.current(subs[i].store.getState(), delta, frame);
1587
- }
1588
- this._frameTime = performance.now() - initialTime;
1589
- }
1590
-
1591
- /**
1592
- * Adds a callback subscriber to the stage.
1593
- * @param ref - The mutable callback reference.
1594
- * @param store - The store to be used with the callback execution.
1595
- * @returns A function to remove the subscription.
1596
- */
1597
- add(ref, store) {
1598
- this.subscribers.push({
1599
- ref,
1600
- store
1601
- });
1602
- return () => {
1603
- this.subscribers = this.subscribers.filter(sub => {
1604
- return sub.ref !== ref;
1605
- });
1606
- };
1607
- }
1608
- get frameTime() {
1609
- return this._frameTime;
1610
- }
1611
- }
1612
-
1613
- // Using Unity's fixedStep default.
1614
- const FPS_50 = 1 / 50;
1615
-
1616
- /**
1617
- * Class representing a stage that updates every frame at a fixed rate.
1618
- * @param name - Name of the stage.
1619
- * @param [fixedStep] - Fixed step rate.
1620
- * @param [maxSubsteps] - Maximum number of substeps.
1621
- */
1622
- class FixedStage extends Stage {
1623
- constructor(fixedStep, maxSubSteps) {
1624
- super();
1625
- this._fixedStep = fixedStep != null ? fixedStep : FPS_50;
1626
- this._maxSubsteps = maxSubSteps != null ? maxSubSteps : 6;
1627
- this._accumulator = 0;
1628
- this._alpha = 0;
1629
- this._fixedFrameTime = 0;
1630
- this._substepTimes = [];
1631
- }
1632
-
1633
- /**
1634
- * Executes all callback subscriptions on the stage.
1635
- * @param delta - Delta time between frame calls.
1636
- * @param [frame] - The XR frame if it exists.
1637
- */
1638
- frame(delta, frame) {
1639
- const initialTime = performance.now();
1640
- let substeps = 0;
1641
- this._substepTimes = [];
1642
- this._accumulator += delta;
1643
- while (this._accumulator >= this._fixedStep && substeps < this._maxSubsteps) {
1644
- this._accumulator -= this._fixedStep;
1645
- substeps++;
1646
- super.frame(this._fixedStep, frame);
1647
- this._substepTimes.push(super.frameTime);
1648
- }
1649
- this._fixedFrameTime = performance.now() - initialTime;
1650
-
1651
- // The accumulator will only be larger than the fixed step if we had to
1652
- // bail early due to hitting the max substep limit or execution time lagging.
1653
- // In that case, we want to shave off the excess so we don't fall behind next frame.
1654
- this._accumulator = this._accumulator % this._fixedStep;
1655
- this._alpha = this._accumulator / this._fixedStep;
1656
- }
1657
- get frameTime() {
1658
- return this._fixedFrameTime;
1659
- }
1660
- get substepTimes() {
1661
- return this._substepTimes;
1662
- }
1663
- get fixedStep() {
1664
- return this._fixedStep;
1665
- }
1666
- set fixedStep(fixedStep) {
1667
- this._fixedStep = fixedStep;
1668
- }
1669
- get maxSubsteps() {
1670
- return this._maxSubsteps;
1671
- }
1672
- set maxSubsteps(maxSubsteps) {
1673
- this._maxSubsteps = maxSubsteps;
1674
- }
1675
- get accumulator() {
1676
- return this._accumulator;
1677
- }
1678
- get alpha() {
1679
- return this._alpha;
1680
- }
1681
- }
1682
- const Early = /*#__PURE__*/new Stage();
1683
- const Fixed = /*#__PURE__*/new FixedStage();
1684
- const Update = /*#__PURE__*/new Stage();
1685
- const Late = /*#__PURE__*/new Stage();
1686
- const Render = /*#__PURE__*/new Stage();
1687
- const After = /*#__PURE__*/new Stage();
1688
- const Stages = {
1689
- Early,
1690
- Fixed,
1691
- Update,
1692
- Late,
1693
- Render,
1694
- After
1695
- };
1696
- const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1697
-
1698
1569
  /**
1699
1570
  * Exposes an object's {@link Instance}.
1700
1571
  * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
@@ -1740,21 +1611,6 @@ function useFrame(callback, renderPriority = 0) {
1740
1611
  return null;
1741
1612
  }
1742
1613
 
1743
- /**
1744
- * Executes a callback in a given update stage.
1745
- * Uses the stage instance to identify which stage to target in the lifecycle.
1746
- */
1747
- function useUpdate(callback, stage = Stages.Update) {
1748
- const store = useStore();
1749
- const stages = store.getState().internal.stages;
1750
- // Memoize ref
1751
- const ref = useMutableCallback(callback);
1752
- // Throw an error if a stage does not exist in the lifecycle
1753
- if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`);
1754
- // Subscribe on mount, unsubscribe on unmount
1755
- useIsomorphicLayoutEffect(() => stage.add(ref, store), [stage]);
1756
- }
1757
-
1758
1614
  /**
1759
1615
  * Returns a node graph of an object with named nodes & materials.
1760
1616
  * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
@@ -1838,42 +1694,6 @@ const createRendererInstance = (gl, canvas) => {
1838
1694
  ...gl
1839
1695
  });
1840
1696
  };
1841
- const createStages = (stages, store) => {
1842
- const state = store.getState();
1843
- let subscribers;
1844
- let subscription;
1845
- const _stages = stages != null ? stages : Lifecycle;
1846
- if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1847
- if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1848
- state.set(({
1849
- internal
1850
- }) => ({
1851
- internal: {
1852
- ...internal,
1853
- stages: _stages
1854
- }
1855
- }));
1856
-
1857
- // Add useFrame loop to update stage
1858
- const frameCallback = {
1859
- current(state, delta, frame) {
1860
- subscribers = state.internal.subscribers;
1861
- for (let i = 0; i < subscribers.length; i++) {
1862
- subscription = subscribers[i];
1863
- subscription.ref.current(subscription.store.getState(), delta, frame);
1864
- }
1865
- }
1866
- };
1867
- Stages.Update.add(frameCallback, store);
1868
-
1869
- // Add render callback to render stage
1870
- const renderCallback = {
1871
- current(state) {
1872
- if (state.internal.render === 'auto' && state.gl.render) state.gl.render(state.scene, state.camera);
1873
- }
1874
- };
1875
- Stages.Render.add(renderCallback, store);
1876
- };
1877
1697
  function computeInitialSize(canvas, size) {
1878
1698
  if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1879
1699
  const {
@@ -1971,8 +1791,7 @@ function createRoot(canvas) {
1971
1791
  performance,
1972
1792
  raycaster: raycastOptions,
1973
1793
  camera: cameraOptions,
1974
- onPointerMissed,
1975
- stages
1794
+ onPointerMissed
1976
1795
  } = props;
1977
1796
  let state = store.getState();
1978
1797
 
@@ -2164,9 +1983,6 @@ function createRoot(canvas) {
2164
1983
  }
2165
1984
  }));
2166
1985
 
2167
- // Create update stages. Only do this once on init
2168
- if (state.internal.stages.length === 0) createStages(stages, store);
2169
-
2170
1986
  // Set locals
2171
1987
  onCreated = onCreatedCallback;
2172
1988
  configured = true;
@@ -2390,21 +2206,30 @@ function flushGlobalEffects(type, timestamp) {
2390
2206
  return run(globalTailEffects, timestamp);
2391
2207
  }
2392
2208
  }
2209
+ let subscribers;
2210
+ let subscription;
2393
2211
  function update(timestamp, state, frame) {
2394
2212
  // Run local effects
2395
2213
  let delta = state.clock.getDelta();
2214
+
2396
2215
  // In frameloop='never' mode, clock times are updated using the provided timestamp
2397
2216
  if (state.frameloop === 'never' && typeof timestamp === 'number') {
2398
2217
  delta = timestamp - state.clock.elapsedTime;
2399
2218
  state.clock.oldTime = state.clock.elapsedTime;
2400
2219
  state.clock.elapsedTime = timestamp;
2401
- } else {
2402
- delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
2403
2220
  }
2404
- // Call subscribers (useUpdate)
2405
- for (const stage of state.internal.stages) {
2406
- stage.frame(delta, frame);
2221
+
2222
+ // Call subscribers (useFrame)
2223
+ subscribers = state.internal.subscribers;
2224
+ for (let i = 0; i < subscribers.length; i++) {
2225
+ subscription = subscribers[i];
2226
+ subscription.ref.current(subscription.store.getState(), delta, frame);
2407
2227
  }
2228
+
2229
+ // Render content
2230
+ if (!state.internal.priority && state.gl.render) state.gl.render(state.scene, state.camera);
2231
+
2232
+ // Decrease frame count
2408
2233
  state.internal.frames = Math.max(0, state.internal.frames - 1);
2409
2234
  return state.frameloop === 'always' ? 1 : state.internal.frames;
2410
2235
  }
@@ -2489,9 +2314,6 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
2489
2314
 
2490
2315
  exports.Block = Block;
2491
2316
  exports.ErrorBoundary = ErrorBoundary;
2492
- exports.FixedStage = FixedStage;
2493
- exports.Stage = Stage;
2494
- exports.Stages = Stages;
2495
2317
  exports._roots = _roots;
2496
2318
  exports.act = act;
2497
2319
  exports.addAfterEffect = addAfterEffect;
@@ -2523,4 +2345,3 @@ exports.useLoader = useLoader;
2523
2345
  exports.useMutableCallback = useMutableCallback;
2524
2346
  exports.useStore = useStore;
2525
2347
  exports.useThree = useThree;
2526
- exports.useUpdate = useUpdate;