@react-three/fiber 9.0.0-alpha.6 → 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,27 +38,28 @@ 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
+
44
+ const createReconciler = Reconciler__default["default"];
45
+
46
+ // TODO: handle constructor overloads
47
+ // https://github.com/pmndrs/react-three-fiber/pull/2931
48
+ // https://github.com/microsoft/TypeScript/issues/37079
49
+
41
50
  const catalogue = {};
42
51
  let i = 0;
43
- const extend = objects => {
44
- if (typeof objects === 'function') {
52
+ const isConstructor$1 = object => typeof object === 'function';
53
+ function extend(objects) {
54
+ if (isConstructor$1(objects)) {
45
55
  const Component = `${i++}`;
46
56
  catalogue[Component] = objects;
47
-
48
- // Returns a component whose name will be inferred in devtools
49
- // @ts-expect-error
50
- return /*#__PURE__*/React__namespace.forwardRef({
51
- [objects.name]: (props, ref) => /*#__PURE__*/jsxRuntime.jsx(Component, {
52
- ...props,
53
- ref: ref
54
- })
55
- }[objects.name]);
57
+ return Component;
56
58
  } else {
57
- return void Object.assign(catalogue, objects);
59
+ Object.assign(catalogue, objects);
58
60
  }
59
- };
60
- function createInstance(type, props, root, flushPrimitive = true) {
61
- var _props$object;
61
+ }
62
+ function validateInstance(type, props) {
62
63
  // Get target from catalogue
63
64
  const name = `${type[0].toUpperCase()}${type.slice(1)}`;
64
65
  const target = catalogue[name];
@@ -71,13 +72,14 @@ function createInstance(type, props, root, flushPrimitive = true) {
71
72
 
72
73
  // Throw if an object or literal was passed for args
73
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);
74
79
 
75
80
  // Regenerate the R3F instance for primitives to simulate a new object
76
- if (flushPrimitive && type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
77
-
78
- // Create instance
79
- const instance = prepare(props.object, root, type, props);
80
- 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);
81
83
  }
82
84
  function hideInstance(instance) {
83
85
  if (!instance.isHidden) {
@@ -174,15 +176,29 @@ function insertBefore(parent, child, beforeChild) {
174
176
  // Attach tree once complete
175
177
  handleContainerEffects(parent, child, beforeChild);
176
178
  }
177
- 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) {
178
196
  if (!child) return;
179
197
 
180
198
  // Unlink instances
181
199
  child.parent = null;
182
- if (recursive === undefined) {
183
- const childIndex = parent.children.indexOf(child);
184
- if (childIndex !== -1) parent.children.splice(childIndex, 1);
185
- }
200
+ const childIndex = parent.children.indexOf(child);
201
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
186
202
 
187
203
  // Eagerly tear down tree
188
204
  if (child.props.attach) {
@@ -196,10 +212,11 @@ function removeChild(parent, child, dispose, recursive) {
196
212
  const shouldDispose = child.props.dispose !== null && dispose !== false;
197
213
 
198
214
  // Recursively remove instance children
199
- if (recursive !== false) {
200
- for (const node of child.children) removeChild(child, node, shouldDispose, true);
201
- 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);
202
218
  }
219
+ child.children.length = 0;
203
220
 
204
221
  // Unlink instance object
205
222
  delete child.object.__r3f;
@@ -211,96 +228,108 @@ function removeChild(parent, child, dispose, recursive) {
211
228
  // - cannot be a <primitive object={...} />
212
229
  // - cannot be a THREE.Scene, because three has broken its own API
213
230
  if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
214
- if (typeof child.object.dispose === 'function') {
215
- const dispose = child.object.dispose.bind(child.object);
216
- const handleDispose = () => {
217
- try {
218
- dispose();
219
- } catch (e) {
220
- // no-op
221
- }
222
- };
223
-
224
- // In a testing environment, cleanup immediately
225
- if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
226
- // Otherwise, using a real GPU so schedule cleanup to prevent stalls
227
- else scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, handleDispose);
228
- }
231
+ disposeOnIdle(child.object);
229
232
  }
230
233
 
231
234
  // Tree was updated, request a frame for top-level instance
232
235
  if (dispose === undefined) invalidateInstance(child);
233
236
  }
234
- function setFiberInstance(fiber, instance) {
235
- if (fiber !== null) {
236
- fiber.stateNode = instance;
237
- 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
+ }
238
248
  }
239
249
  }
240
- function switchInstance(oldInstance, type, props, fiber) {
241
- // If the old instance is hidden, we need to unhide it.
242
- // React assumes it can discard instances since they're pure for DOM.
243
- // This isn't true for us since our lifetimes are impure and longliving.
244
- // So, we manually check if an instance was hidden and unhide it.
245
- if (oldInstance.isHidden) unhideInstance(oldInstance);
246
-
247
- // Create a new instance
248
- const newInstance = createInstance(type, props, oldInstance.root, false);
249
-
250
- // Update attach props for primitives since we don't flush them
251
- if (type === 'primitive') {
252
- newInstance.props.attach = props.attach;
253
- }
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
+ }
254
269
 
255
- // Move children to new instance
256
- for (const child of oldInstance.children) {
257
- removeChild(oldInstance, child, false, false);
258
- appendChild(newInstance, child);
259
- }
260
- oldInstance.children.length = 0;
261
-
262
- // Link up new instance
263
- const parent = oldInstance.parent;
264
- if (parent) {
265
- // Manually handle replace https://github.com/pmndrs/react-three-fiber/pull/2680
266
-
267
- newInstance.autoRemovedBeforeAppend = !!newInstance.parent;
268
- if (!oldInstance.autoRemovedBeforeAppend) removeChild(parent, oldInstance);
269
- appendChild(parent, newInstance);
270
-
271
- // if (!oldInstance.autoRemovedBeforeAppend) {
272
- // insertBefore(parent, newInstance, oldInstance)
273
- // removeChild(parent, oldInstance)
274
- // } else {
275
- // appendChild(parent, newInstance)
276
- // }
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);
277
279
  }
278
280
 
279
- // This evil hack switches the react-internal fiber instance
280
- // https://github.com/facebook/react/issues/14983
281
- // TODO: investigate scheduling key prop change instead of switchInstance entirely
282
- // https://github.com/facebook/react/pull/15021#issuecomment-480185369
283
- setFiberInstance(fiber, newInstance);
284
- 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
+ }
285
310
 
286
- // Tree was updated, request a frame
287
- invalidateInstance(newInstance);
288
- return newInstance;
311
+ // Tree was updated, request a frame
312
+ invalidateInstance(instance);
313
+ }
314
+ }
315
+ reconstructed.length = 0;
289
316
  }
290
317
 
291
318
  // Don't handle text instances, warn on undefined behavior
292
319
  const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
293
320
  const NO_CONTEXT = {};
294
321
  let currentUpdatePriority = constants.NoEventPriority;
295
- const reconciler = Reconciler__default["default"]({
322
+
323
+ // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
324
+ const NoFlags = 0;
325
+ const Update = 4;
326
+ const reconciler = createReconciler({
296
327
  isPrimaryRenderer: false,
297
328
  warnsIfNotActing: false,
298
329
  supportsMutation: true,
299
330
  supportsPersistence: false,
300
331
  supportsHydration: false,
301
- createInstance(type, props, root) {
302
- return createInstance(type, props, root);
303
- },
332
+ createInstance,
304
333
  removeChild,
305
334
  appendChild,
306
335
  appendInitialChild: appendChild,
@@ -322,15 +351,13 @@ const reconciler = Reconciler__default["default"]({
322
351
  },
323
352
  getRootHostContext: () => NO_CONTEXT,
324
353
  getChildHostContext: () => NO_CONTEXT,
325
- // @ts-expect-error prepareUpdate and updatePayload removed with React 19
326
354
  commitUpdate(instance, type, oldProps, newProps, fiber) {
327
355
  var _newProps$args, _oldProps$args, _newProps$args2;
356
+ validateInstance(type, newProps);
328
357
  let reconstruct = false;
329
358
 
330
359
  // Reconstruct primitives if object prop changes
331
360
  if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
332
- // Reconstruct instance if args was changed to an invalid value
333
- else if (newProps.args !== undefined && !Array.isArray(newProps.args)) reconstruct = true;
334
361
  // Reconstruct instance if args were added or removed
335
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;
336
363
  // Reconstruct instance if args were changed
@@ -340,14 +367,22 @@ const reconciler = Reconciler__default["default"]({
340
367
  })) reconstruct = true;
341
368
 
342
369
  // Reconstruct when args or <primitive object={...} have changes
343
- if (reconstruct) return switchInstance(instance, type, newProps, fiber);
344
-
345
- // Create a diff-set, flag if there are any changes
346
- const changedProps = diffProps(instance, newProps);
347
- if (Object.keys(changedProps).length) {
348
- Object.assign(instance.props, changedProps);
349
- 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
+ }
350
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();
351
386
  },
352
387
  finalizeInitialChildren: () => false,
353
388
  commitMount() {},
@@ -369,8 +404,8 @@ const reconciler = Reconciler__default["default"]({
369
404
  beforeActiveInstanceBlur() {},
370
405
  afterActiveInstanceBlur() {},
371
406
  detachDeletedInstance() {},
372
- // TODO: add shell types for these and upstream to DefinitelyTyped
373
- // https://github.com/facebook/react/blob/main/packages/react-art/src/ReactFiberConfigART.js
407
+ prepareScopeUpdate() {},
408
+ getInstanceFromScope: () => null,
374
409
  shouldAttemptEagerTransition() {
375
410
  return false;
376
411
  },
@@ -414,7 +449,8 @@ const reconciler = Reconciler__default["default"]({
414
449
  default:
415
450
  return constants.DefaultEventPriority;
416
451
  }
417
- }
452
+ },
453
+ resetFormInstance() {}
418
454
  });
419
455
 
420
456
  var _window$document, _window$navigator;
@@ -691,7 +727,7 @@ function detach(parent, child) {
691
727
  }
692
728
  const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
693
729
  // Instance props
694
- 'args', 'dispose', 'attach', 'object',
730
+ 'args', 'dispose', 'attach', 'object', 'onUpdate',
695
731
  // Behavior flags
696
732
  'dispose'];
697
733
  const MEMOIZED_PROTOTYPES = new Map();
@@ -865,6 +901,8 @@ function applyProps(object, props) {
865
901
  }
866
902
  function invalidateInstance(instance) {
867
903
  var _instance$root;
904
+ if (!instance.parent) return;
905
+ instance.props.onUpdate == null ? void 0 : instance.props.onUpdate(instance.object);
868
906
  const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
869
907
  if (state && state.internal.frames === 0) state.invalidate();
870
908
  }
@@ -1427,13 +1465,9 @@ const createStore = (invalidate, advance) => {
1427
1465
  }
1428
1466
  };
1429
1467
  }),
1430
- setFrameloop: frameloop => {
1431
- var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
1432
- const state = get();
1433
- 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;
1434
- const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
1435
- const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
1436
- const clock = state.clock;
1468
+ setFrameloop: (frameloop = 'always') => {
1469
+ const clock = get().clock;
1470
+
1437
1471
  // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1438
1472
  clock.stop();
1439
1473
  clock.elapsedTime = 0;
@@ -1442,12 +1476,7 @@ const createStore = (invalidate, advance) => {
1442
1476
  clock.elapsedTime = 0;
1443
1477
  }
1444
1478
  set(() => ({
1445
- frameloop: mode,
1446
- internal: {
1447
- ...state.internal,
1448
- render,
1449
- maxDelta
1450
- }
1479
+ frameloop
1451
1480
  }));
1452
1481
  },
1453
1482
  previousRoot: undefined,
@@ -1463,25 +1492,14 @@ const createStore = (invalidate, advance) => {
1463
1492
  // Updates
1464
1493
  active: false,
1465
1494
  frames: 0,
1466
- stages: [],
1467
- render: 'auto',
1468
- maxDelta: 1 / 10,
1469
1495
  priority: 0,
1470
1496
  subscribe: (ref, priority, store) => {
1471
- const state = get();
1472
- const internal = state.internal;
1497
+ const internal = get().internal;
1473
1498
  // If this subscription was given a priority, it takes rendering into its own hands
1474
1499
  // For that reason we switch off automatic rendering and increase the manual flag
1475
1500
  // As long as this flag is positive there can be no internal rendering at all
1476
1501
  // because there could be multiple render subscriptions
1477
1502
  internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1478
- // We use the render flag and deprecate priority
1479
- if (internal.priority && state.internal.render === 'auto') set(() => ({
1480
- internal: {
1481
- ...state.internal,
1482
- render: 'manual'
1483
- }
1484
- }));
1485
1503
  internal.subscribers.push({
1486
1504
  ref,
1487
1505
  priority,
@@ -1491,18 +1509,10 @@ const createStore = (invalidate, advance) => {
1491
1509
  // highest priority renders last (on top of the other frames)
1492
1510
  internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1493
1511
  return () => {
1494
- const state = get();
1495
- const internal = state.internal;
1512
+ const internal = get().internal;
1496
1513
  if (internal != null && internal.subscribers) {
1497
1514
  // Decrease manual flag if this subscription had a priority
1498
1515
  internal.priority = internal.priority - (priority > 0 ? 1 : 0);
1499
- // We use the render flag and deprecate priority
1500
- if (!internal.priority && state.internal.render === 'manual') set(() => ({
1501
- internal: {
1502
- ...state.internal,
1503
- render: 'auto'
1504
- }
1505
- }));
1506
1516
  // Remove subscriber from list
1507
1517
  internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
1508
1518
  }
@@ -1556,139 +1566,6 @@ const createStore = (invalidate, advance) => {
1556
1566
  return rootStore;
1557
1567
  };
1558
1568
 
1559
- // TODO: Remove deprecated fields in `Subscription`
1560
-
1561
- /**
1562
- * Class representing a stage that updates every frame.
1563
- * Stages are used to build a lifecycle of effects for an app's frameloop.
1564
- */
1565
- class Stage {
1566
- constructor() {
1567
- this.subscribers = [];
1568
- this._frameTime = 0;
1569
- }
1570
-
1571
- /**
1572
- * Executes all callback subscriptions on the stage.
1573
- * @param delta - Delta time between frame calls.
1574
- * @param [frame] - The XR frame if it exists.
1575
- */
1576
- frame(delta, frame) {
1577
- const subs = this.subscribers;
1578
- const initialTime = performance.now();
1579
- for (let i = 0; i < subs.length; i++) {
1580
- subs[i].ref.current(subs[i].store.getState(), delta, frame);
1581
- }
1582
- this._frameTime = performance.now() - initialTime;
1583
- }
1584
-
1585
- /**
1586
- * Adds a callback subscriber to the stage.
1587
- * @param ref - The mutable callback reference.
1588
- * @param store - The store to be used with the callback execution.
1589
- * @returns A function to remove the subscription.
1590
- */
1591
- add(ref, store) {
1592
- this.subscribers.push({
1593
- ref,
1594
- store
1595
- });
1596
- return () => {
1597
- this.subscribers = this.subscribers.filter(sub => {
1598
- return sub.ref !== ref;
1599
- });
1600
- };
1601
- }
1602
- get frameTime() {
1603
- return this._frameTime;
1604
- }
1605
- }
1606
-
1607
- // Using Unity's fixedStep default.
1608
- const FPS_50 = 1 / 50;
1609
-
1610
- /**
1611
- * Class representing a stage that updates every frame at a fixed rate.
1612
- * @param name - Name of the stage.
1613
- * @param [fixedStep] - Fixed step rate.
1614
- * @param [maxSubsteps] - Maximum number of substeps.
1615
- */
1616
- class FixedStage extends Stage {
1617
- constructor(fixedStep, maxSubSteps) {
1618
- super();
1619
- this._fixedStep = fixedStep != null ? fixedStep : FPS_50;
1620
- this._maxSubsteps = maxSubSteps != null ? maxSubSteps : 6;
1621
- this._accumulator = 0;
1622
- this._alpha = 0;
1623
- this._fixedFrameTime = 0;
1624
- this._substepTimes = [];
1625
- }
1626
-
1627
- /**
1628
- * Executes all callback subscriptions on the stage.
1629
- * @param delta - Delta time between frame calls.
1630
- * @param [frame] - The XR frame if it exists.
1631
- */
1632
- frame(delta, frame) {
1633
- const initialTime = performance.now();
1634
- let substeps = 0;
1635
- this._substepTimes = [];
1636
- this._accumulator += delta;
1637
- while (this._accumulator >= this._fixedStep && substeps < this._maxSubsteps) {
1638
- this._accumulator -= this._fixedStep;
1639
- substeps++;
1640
- super.frame(this._fixedStep, frame);
1641
- this._substepTimes.push(super.frameTime);
1642
- }
1643
- this._fixedFrameTime = performance.now() - initialTime;
1644
-
1645
- // The accumulator will only be larger than the fixed step if we had to
1646
- // bail early due to hitting the max substep limit or execution time lagging.
1647
- // In that case, we want to shave off the excess so we don't fall behind next frame.
1648
- this._accumulator = this._accumulator % this._fixedStep;
1649
- this._alpha = this._accumulator / this._fixedStep;
1650
- }
1651
- get frameTime() {
1652
- return this._fixedFrameTime;
1653
- }
1654
- get substepTimes() {
1655
- return this._substepTimes;
1656
- }
1657
- get fixedStep() {
1658
- return this._fixedStep;
1659
- }
1660
- set fixedStep(fixedStep) {
1661
- this._fixedStep = fixedStep;
1662
- }
1663
- get maxSubsteps() {
1664
- return this._maxSubsteps;
1665
- }
1666
- set maxSubsteps(maxSubsteps) {
1667
- this._maxSubsteps = maxSubsteps;
1668
- }
1669
- get accumulator() {
1670
- return this._accumulator;
1671
- }
1672
- get alpha() {
1673
- return this._alpha;
1674
- }
1675
- }
1676
- const Early = /*#__PURE__*/new Stage();
1677
- const Fixed = /*#__PURE__*/new FixedStage();
1678
- const Update = /*#__PURE__*/new Stage();
1679
- const Late = /*#__PURE__*/new Stage();
1680
- const Render = /*#__PURE__*/new Stage();
1681
- const After = /*#__PURE__*/new Stage();
1682
- const Stages = {
1683
- Early,
1684
- Fixed,
1685
- Update,
1686
- Late,
1687
- Render,
1688
- After
1689
- };
1690
- const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1691
-
1692
1569
  /**
1693
1570
  * Exposes an object's {@link Instance}.
1694
1571
  * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
@@ -1734,21 +1611,6 @@ function useFrame(callback, renderPriority = 0) {
1734
1611
  return null;
1735
1612
  }
1736
1613
 
1737
- /**
1738
- * Executes a callback in a given update stage.
1739
- * Uses the stage instance to identify which stage to target in the lifecycle.
1740
- */
1741
- function useUpdate(callback, stage = Stages.Update) {
1742
- const store = useStore();
1743
- const stages = store.getState().internal.stages;
1744
- // Memoize ref
1745
- const ref = useMutableCallback(callback);
1746
- // Throw an error if a stage does not exist in the lifecycle
1747
- if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`);
1748
- // Subscribe on mount, unsubscribe on unmount
1749
- useIsomorphicLayoutEffect(() => stage.add(ref, store), [stage]);
1750
- }
1751
-
1752
1614
  /**
1753
1615
  * Returns a node graph of an object with named nodes & materials.
1754
1616
  * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
@@ -1832,42 +1694,6 @@ const createRendererInstance = (gl, canvas) => {
1832
1694
  ...gl
1833
1695
  });
1834
1696
  };
1835
- const createStages = (stages, store) => {
1836
- const state = store.getState();
1837
- let subscribers;
1838
- let subscription;
1839
- const _stages = stages != null ? stages : Lifecycle;
1840
- if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1841
- if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1842
- state.set(({
1843
- internal
1844
- }) => ({
1845
- internal: {
1846
- ...internal,
1847
- stages: _stages
1848
- }
1849
- }));
1850
-
1851
- // Add useFrame loop to update stage
1852
- const frameCallback = {
1853
- current(state, delta, frame) {
1854
- subscribers = state.internal.subscribers;
1855
- for (let i = 0; i < subscribers.length; i++) {
1856
- subscription = subscribers[i];
1857
- subscription.ref.current(subscription.store.getState(), delta, frame);
1858
- }
1859
- }
1860
- };
1861
- Stages.Update.add(frameCallback, store);
1862
-
1863
- // Add render callback to render stage
1864
- const renderCallback = {
1865
- current(state) {
1866
- if (state.internal.render === 'auto' && state.gl.render) state.gl.render(state.scene, state.camera);
1867
- }
1868
- };
1869
- Stages.Render.add(renderCallback, store);
1870
- };
1871
1697
  function computeInitialSize(canvas, size) {
1872
1698
  if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1873
1699
  const {
@@ -1965,8 +1791,7 @@ function createRoot(canvas) {
1965
1791
  performance,
1966
1792
  raycaster: raycastOptions,
1967
1793
  camera: cameraOptions,
1968
- onPointerMissed,
1969
- stages
1794
+ onPointerMissed
1970
1795
  } = props;
1971
1796
  let state = store.getState();
1972
1797
 
@@ -2008,9 +1833,11 @@ function createRoot(canvas) {
2008
1833
  applyProps(camera, cameraOptions);
2009
1834
  // Preserve user-defined frustum if possible
2010
1835
  // https://github.com/pmndrs/react-three-fiber/issues/3160
2011
- if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
2012
- camera.manual = true;
2013
- camera.updateProjectionMatrix();
1836
+ if (!camera.manual) {
1837
+ if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
1838
+ camera.manual = true;
1839
+ camera.updateProjectionMatrix();
1840
+ }
2014
1841
  }
2015
1842
  }
2016
1843
  // Always look at center by default
@@ -2156,9 +1983,6 @@ function createRoot(canvas) {
2156
1983
  }
2157
1984
  }));
2158
1985
 
2159
- // Create update stages. Only do this once on init
2160
- if (state.internal.stages.length === 0) createStages(stages, store);
2161
-
2162
1986
  // Set locals
2163
1987
  onCreated = onCreatedCallback;
2164
1988
  configured = true;
@@ -2265,7 +2089,7 @@ function Portal({
2265
2089
  const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
2266
2090
  const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
2267
2091
  const inject = useMutableCallback((rootState, injectState) => {
2268
- let viewport;
2092
+ let viewport = undefined;
2269
2093
  if (injectState.camera && size) {
2270
2094
  const camera = injectState.camera;
2271
2095
  // Calculate the override viewport, if present
@@ -2276,8 +2100,7 @@ function Portal({
2276
2100
  return {
2277
2101
  // The intersect consists of the previous root state
2278
2102
  ...rootState,
2279
- get: injectState.get,
2280
- set: injectState.set,
2103
+ ...injectState,
2281
2104
  // Portals have their own scene, which forms the root, a raycaster and a pointer
2282
2105
  scene: container,
2283
2106
  raycaster,
@@ -2310,6 +2133,7 @@ function Portal({
2310
2133
  };
2311
2134
  });
2312
2135
  const usePortalStore = React__namespace.useMemo(() => {
2136
+ // Create a mirrored store, based on the previous root with a few overrides ...
2313
2137
  const store = traditional.createWithEqualityFn((set, get) => ({
2314
2138
  ...rest,
2315
2139
  set,
@@ -2382,21 +2206,30 @@ function flushGlobalEffects(type, timestamp) {
2382
2206
  return run(globalTailEffects, timestamp);
2383
2207
  }
2384
2208
  }
2209
+ let subscribers;
2210
+ let subscription;
2385
2211
  function update(timestamp, state, frame) {
2386
2212
  // Run local effects
2387
2213
  let delta = state.clock.getDelta();
2214
+
2388
2215
  // In frameloop='never' mode, clock times are updated using the provided timestamp
2389
2216
  if (state.frameloop === 'never' && typeof timestamp === 'number') {
2390
2217
  delta = timestamp - state.clock.elapsedTime;
2391
2218
  state.clock.oldTime = state.clock.elapsedTime;
2392
2219
  state.clock.elapsedTime = timestamp;
2393
- } else {
2394
- delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
2395
2220
  }
2396
- // Call subscribers (useUpdate)
2397
- for (const stage of state.internal.stages) {
2398
- 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);
2399
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
2400
2233
  state.internal.frames = Math.max(0, state.internal.frames - 1);
2401
2234
  return state.frameloop === 'always' ? 1 : state.internal.frames;
2402
2235
  }
@@ -2481,9 +2314,6 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
2481
2314
 
2482
2315
  exports.Block = Block;
2483
2316
  exports.ErrorBoundary = ErrorBoundary;
2484
- exports.FixedStage = FixedStage;
2485
- exports.Stage = Stage;
2486
- exports.Stages = Stages;
2487
2317
  exports._roots = _roots;
2488
2318
  exports.act = act;
2489
2319
  exports.addAfterEffect = addAfterEffect;
@@ -2515,4 +2345,3 @@ exports.useLoader = useLoader;
2515
2345
  exports.useMutableCallback = useMutableCallback;
2516
2346
  exports.useStore = useStore;
2517
2347
  exports.useThree = useThree;
2518
- exports.useUpdate = useUpdate;