@samitouri / QOS-React-2 / commits / 93e078ddf2

Initial Lanes implementation (#18796)

See PR #18796 for more information. All of the changes I've made in this commit are behind the `enableNewReconciler` flag. Merging this to master will not affect the open source builds or the build that we ship to Facebook. The only build that is affected is the `ReactDOMForked` build, which is deployed to Facebook **behind an experimental flag (currently disabled for all users)**. We will use this flag to gradually roll out the new reconciler, and quickly roll it back if we find any problems. Because we have those protections in place, what I'm aiming for with this initial PR is the **smallest possible atomic change that lands cleanly and doesn't rely on too many hacks**. The goal has not been to get every single test or feature passing, and it definitely is not to implement all the features that we intend to build on top of the new model. When possible, I have chosen to preserve existing semantics and defer changes to follow-up steps. (Listed in the section below.) (I did not end up having to disable any tests, although if I had, that should not have necessarily been a merge blocker.) For example, even though one of the primary goals of this project is to improve our model for parallel Suspense transitions, in this initial implementation, I have chosen to keep the same core heuristics for sequencing and flushing that existed in the ExpirationTimes model: low priority updates cannot finish without also finishing high priority ones. Despite all these precautions, **because the scope of this refactor is inherently large, I do expect we will find regressions.** The flip side is that I also expect the new model to improve the stability of the codebase and make it easier to fix bugs when they arise.

Andrew Clark committed May 2, 2020 at 17:09 UTC 93e078ddf274636b0a40bd5501ce3549aec700fa
28 files changed +1956 -2271
packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
+51 -52
@@ -1306,44 +1306,25 @@ describe('ReactDOMServerHooks', () => {
1306 // State update should trigger the ID to update, which changes the props
1307 // of ChildWithID. This should cause ChildWithID to hydrate before Children
1308
1309 - gate(flags => {
1310 - if (__DEV__) {
1311 - expect(Scheduler).toFlushAndYieldThrough([
1312 - 'Child with ID',
1313 - // Fallbacks are immdiately committed in TestUtils version
1314 - // of act
1315 - // 'Child with ID',
1316 - // 'Child with ID',
1317 - 'Child One',
1318 - 'Child Two',
1319 - ]);
1320 - } else if (flags.new) {
1321 - // Upgrading a dehyrdating boundary works a little differently in
1322 - // the new reconciler. After the update on the boundary is
1323 - // scheduled, it waits until the end of the current time slice
1324 - // before restarting at the higher priority.
1325 - expect(Scheduler).toFlushAndYieldThrough([
1326 - 'Child with ID',
1327 - 'Child with ID',
1328 - 'Child with ID',
1329 - 'Child with ID',
1330 - 'Child One',
1331 - 'Child Two',
1332 - ]);
1333 - } else {
1334 - // Whereas the old reconciler relies on a Scheduler hack to
1335 - // interrupt the current task. It's not clear if this is any
1336 - // better or worse, though. Regardless it's not a big deal since
1337 - // the time slices aren't that big.
1338 - expect(Scheduler).toFlushAndYieldThrough([
1339 - 'Child with ID',
1340 - 'Child with ID',
1341 - 'Child with ID',
1342 - 'Child One',
1343 - 'Child Two',
1344 - ]);
1345 - }
1346 - });
1309 + expect(Scheduler).toFlushAndYieldThrough(
1310 + __DEV__
1311 + ? [
1312 + 'Child with ID',
1313 + // Fallbacks are immediately committed in TestUtils version
1314 + // of act
1315 + // 'Child with ID',
1316 + // 'Child with ID',
1317 + 'Child One',
1318 + 'Child Two',
1319 + ]
1320 + : [
1321 + 'Child with ID',
1322 + 'Child with ID',
1323 + 'Child with ID',
1324 + 'Child One',
1325 + 'Child Two',
1326 + ],
1327 + );
1328
1329 expect(child1Ref.current).toBe(null);
1330 expect(childWithIDRef.current).toEqual(
@@ -1691,15 +1672,24 @@ describe('ReactDOMServerHooks', () => {
1672
1673 ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
1674
1694 - expect(() =>
1695 - expect(() => Scheduler.unstable_flushAll()).toThrow(
1675 + if (gate(flags => flags.new)) {
1676 + expect(() => Scheduler.unstable_flushAll()).toErrorDev([
1677 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1678 'Do not read the value directly.',
1698 - ),
1699 - ).toErrorDev([
1700 - 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1701 - 'Do not read the value directly.',
1702 - ]);
1679 + ]);
1680 + } else {
1681 + // In the old reconciler, the error isn't surfaced to the user. That
1682 + // part isn't important, as long as It warns.
1683 + expect(() =>
1684 + expect(() => Scheduler.unstable_flushAll()).toThrow(
1685 + 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1686 + 'Do not read the value directly.',
1687 + ),
1688 + ).toErrorDev([
1689 + 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1690 + 'Do not read the value directly.',
1691 + ]);
1692 + }
1693 });
1694
1695 it('useOpaqueIdentifier throws if you try to add the result as a number in a child component wrapped in a Suspense', async () => {
@@ -1724,15 +1714,24 @@ describe('ReactDOMServerHooks', () => {
1714
1715 ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
1716
1727 - expect(() =>
1728 - expect(() => Scheduler.unstable_flushAll()).toThrow(
1717 + if (gate(flags => flags.new)) {
1718 + expect(() => Scheduler.unstable_flushAll()).toErrorDev([
1719 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1720 'Do not read the value directly.',
1731 - ),
1732 - ).toErrorDev([
1733 - 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1734 - 'Do not read the value directly.',
1735 - ]);
1721 + ]);
1722 + } else {
1723 + // In the old reconciler, the error isn't surfaced to the user. That
1724 + // part isn't important, as long as It warns.
1725 + expect(() =>
1726 + expect(() => Scheduler.unstable_flushAll()).toThrow(
1727 + 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1728 + 'Do not read the value directly.',
1729 + ),
1730 + ).toErrorDev([
1731 + 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1732 + 'Do not read the value directly.',
1733 + ]);
1734 + }
1735 });
1736
1737 it('useOpaqueIdentifier with two opaque identifiers on the same page', () => {
packages/react-reconciler/src/ReactChildFiber.new.js
+49 -127
@@ -12,7 +12,7 @@ import type {ReactPortal} from 'shared/ReactTypes';
12 import type {BlockComponent} from 'react/src/ReactBlock';
13 import type {LazyComponent} from 'react/src/ReactLazy';
14 import type {Fiber} from './ReactInternalTypes';
15 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
15 +import type {Lanes} from './ReactFiberLane';
16
17 import getComponentName from 'shared/getComponentName';
18 import {Placement, Deletion} from './ReactSideEffectTags';
@@ -377,15 +377,11 @@ function ChildReconciler(shouldTrackSideEffects) {
377 returnFiber: Fiber,
378 current: Fiber | null,
379 textContent: string,
380 - expirationTime: ExpirationTimeOpaque,
380 + lanes: Lanes,
381 ) {
382 if (current === null || current.tag !== HostText) {
383 // Insert
384 - const created = createFiberFromText(
385 - textContent,
386 - returnFiber.mode,
387 - expirationTime,
388 - );
384 + const created = createFiberFromText(textContent, returnFiber.mode, lanes);
385 created.return = returnFiber;
386 return created;
387 } else {
@@ -400,7 +396,7 @@ function ChildReconciler(shouldTrackSideEffects) {
396 returnFiber: Fiber,
397 current: Fiber | null,
398 element: ReactElement,
403 - expirationTime: ExpirationTimeOpaque,
399 + lanes: Lanes,
400 ): Fiber {
401 if (current !== null) {
402 if (
@@ -442,11 +438,7 @@ function ChildReconciler(shouldTrackSideEffects) {
438 }
439 }
440 // Insert
445 - const created = createFiberFromElement(
446 - element,
447 - returnFiber.mode,
448 - expirationTime,
449 - );
441 + const created = createFiberFromElement(element, returnFiber.mode, lanes);
442 created.ref = coerceRef(returnFiber, current, element);
443 created.return = returnFiber;
444 return created;
@@ -456,7 +448,7 @@ function ChildReconciler(shouldTrackSideEffects) {
448 returnFiber: Fiber,
449 current: Fiber | null,
450 portal: ReactPortal,
459 - expirationTime: ExpirationTimeOpaque,
451 + lanes: Lanes,
452 ): Fiber {
453 if (
454 current === null ||
@@ -465,11 +457,7 @@ function ChildReconciler(shouldTrackSideEffects) {
457 current.stateNode.implementation !== portal.implementation
458 ) {
459 // Insert
468 - const created = createFiberFromPortal(
469 - portal,
470 - returnFiber.mode,
471 - expirationTime,
472 - );
460 + const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
461 created.return = returnFiber;
462 return created;
463 } else {
@@ -484,7 +472,7 @@ function ChildReconciler(shouldTrackSideEffects) {
472 returnFiber: Fiber,
473 current: Fiber | null,
474 fragment: Iterable<*>,
487 - expirationTime: ExpirationTimeOpaque,
475 + lanes: Lanes,
476 key: null | string,
477 ): Fiber {
478 if (current === null || current.tag !== Fragment) {
@@ -492,7 +480,7 @@ function ChildReconciler(shouldTrackSideEffects) {
480 const created = createFiberFromFragment(
481 fragment,
482 returnFiber.mode,
495 - expirationTime,
483 + lanes,
484 key,
485 );
486 created.return = returnFiber;
@@ -508,7 +496,7 @@ function ChildReconciler(shouldTrackSideEffects) {
496 function createChild(
497 returnFiber: Fiber,
498 newChild: any,
511 - expirationTime: ExpirationTimeOpaque,
499 + lanes: Lanes,
500 ): Fiber | null {
501 if (typeof newChild === 'string' || typeof newChild === 'number') {
502 // Text nodes don't have keys. If the previous node is implicitly keyed
@@ -517,7 +505,7 @@ function ChildReconciler(shouldTrackSideEffects) {
505 const created = createFiberFromText(
506 '' + newChild,
507 returnFiber.mode,
520 - expirationTime,
508 + lanes,
509 );
510 created.return = returnFiber;
511 return created;
@@ -529,7 +517,7 @@ function ChildReconciler(shouldTrackSideEffects) {
517 const created = createFiberFromElement(
518 newChild,
519 returnFiber.mode,
532 - expirationTime,
520 + lanes,
521 );
522 created.ref = coerceRef(returnFiber, null, newChild);
523 created.return = returnFiber;
@@ -539,7 +527,7 @@ function ChildReconciler(shouldTrackSideEffects) {
527 const created = createFiberFromPortal(
528 newChild,
529 returnFiber.mode,
542 - expirationTime,
530 + lanes,
531 );
532 created.return = returnFiber;
533 return created;
@@ -550,7 +538,7 @@ function ChildReconciler(shouldTrackSideEffects) {
538 const created = createFiberFromFragment(
539 newChild,
540 returnFiber.mode,
553 - expirationTime,
541 + lanes,
542 null,
543 );
544 created.return = returnFiber;
@@ -573,7 +561,7 @@ function ChildReconciler(shouldTrackSideEffects) {
561 returnFiber: Fiber,
562 oldFiber: Fiber | null,
563 newChild: any,
576 - expirationTime: ExpirationTimeOpaque,
564 + lanes: Lanes,
565 ): Fiber | null {
566 // Update the fiber if the keys match, otherwise return null.
567
@@ -586,12 +574,7 @@ function ChildReconciler(shouldTrackSideEffects) {
574 if (key !== null) {
575 return null;
576 }
589 - return updateTextNode(
590 - returnFiber,
591 - oldFiber,
592 - '' + newChild,
593 - expirationTime,
594 - );
577 + return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
578 }
579
580 if (typeof newChild === 'object' && newChild !== null) {
@@ -603,28 +586,18 @@ function ChildReconciler(shouldTrackSideEffects) {
586 returnFiber,
587 oldFiber,
588 newChild.props.children,
606 - expirationTime,
589 + lanes,
590 key,
591 );
592 }
610 - return updateElement(
611 - returnFiber,
612 - oldFiber,
613 - newChild,
614 - expirationTime,
615 - );
593 + return updateElement(returnFiber, oldFiber, newChild, lanes);
594 } else {
595 return null;
596 }
597 }
598 case REACT_PORTAL_TYPE: {
599 if (newChild.key === key) {
622 - return updatePortal(
623 - returnFiber,
624 - oldFiber,
625 - newChild,
626 - expirationTime,
627 - );
600 + return updatePortal(returnFiber, oldFiber, newChild, lanes);
601 } else {
602 return null;
603 }
@@ -636,13 +609,7 @@ function ChildReconciler(shouldTrackSideEffects) {
609 return null;
610 }
611
639 - return updateFragment(
640 - returnFiber,
641 - oldFiber,
642 - newChild,
643 - expirationTime,
644 - null,
645 - );
612 + return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
613 }
614
615 throwOnInvalidObjectType(returnFiber, newChild);
@@ -662,18 +629,13 @@ function ChildReconciler(shouldTrackSideEffects) {
629 returnFiber: Fiber,
630 newIdx: number,
631 newChild: any,
665 - expirationTime: ExpirationTimeOpaque,
632 + lanes: Lanes,
633 ): Fiber | null {
634 if (typeof newChild === 'string' || typeof newChild === 'number') {
635 // Text nodes don't have keys, so we neither have to check the old nor
636 // new node for the key. If both are text nodes, they match.
637 const matchedFiber = existingChildren.get(newIdx) || null;
671 - return updateTextNode(
672 - returnFiber,
673 - matchedFiber,
674 - '' + newChild,
675 - expirationTime,
676 - );
638 + return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes);
639 }
640
641 if (typeof newChild === 'object' && newChild !== null) {
@@ -688,40 +650,24 @@ function ChildReconciler(shouldTrackSideEffects) {
650 returnFiber,
651 matchedFiber,
652 newChild.props.children,
691 - expirationTime,
653 + lanes,
654 newChild.key,
655 );
656 }
695 - return updateElement(
696 - returnFiber,
697 - matchedFiber,
698 - newChild,
699 - expirationTime,
700 - );
657 + return updateElement(returnFiber, matchedFiber, newChild, lanes);
658 }
659 case REACT_PORTAL_TYPE: {
660 const matchedFiber =
661 existingChildren.get(
662 newChild.key === null ? newIdx : newChild.key,
663 ) || null;
707 - return updatePortal(
708 - returnFiber,
709 - matchedFiber,
710 - newChild,
711 - expirationTime,
712 - );
664 + return updatePortal(returnFiber, matchedFiber, newChild, lanes);
665 }
666 }
667
668 if (isArray(newChild) || getIteratorFn(newChild)) {
669 const matchedFiber = existingChildren.get(newIdx) || null;
718 - return updateFragment(
719 - returnFiber,
720 - matchedFiber,
721 - newChild,
722 - expirationTime,
723 - null,
724 - );
670 + return updateFragment(returnFiber, matchedFiber, newChild, lanes, null);
671 }
672
673 throwOnInvalidObjectType(returnFiber, newChild);
@@ -785,7 +731,7 @@ function ChildReconciler(shouldTrackSideEffects) {
731 returnFiber: Fiber,
732 currentFirstChild: Fiber | null,
733 newChildren: Array<*>,
788 - expirationTime: ExpirationTimeOpaque,
734 + lanes: Lanes,
735 ): Fiber | null {
736 // This algorithm can't optimize by searching from both ends since we
737 // don't have backpointers on fibers. I'm trying to see how far we can get
@@ -833,7 +779,7 @@ function ChildReconciler(shouldTrackSideEffects) {
779 returnFiber,
780 oldFiber,
781 newChildren[newIdx],
836 - expirationTime,
782 + lanes,
783 );
784 if (newFiber === null) {
785 // TODO: This breaks on empty slots like null children. That's
@@ -877,11 +823,7 @@ function ChildReconciler(shouldTrackSideEffects) {
823 // If we don't have any more existing children we can choose a fast path
824 // since the rest will all be insertions.
825 for (; newIdx < newChildren.length; newIdx++) {
880 - const newFiber = createChild(
881 - returnFiber,
882 - newChildren[newIdx],
883 - expirationTime,
884 - );
826 + const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
827 if (newFiber === null) {
828 continue;
829 }
@@ -907,7 +849,7 @@ function ChildReconciler(shouldTrackSideEffects) {
849 returnFiber,
850 newIdx,
851 newChildren[newIdx],
910 - expirationTime,
852 + lanes,
853 );
854 if (newFiber !== null) {
855 if (shouldTrackSideEffects) {
@@ -944,7 +886,7 @@ function ChildReconciler(shouldTrackSideEffects) {
886 returnFiber: Fiber,
887 currentFirstChild: Fiber | null,
888 newChildrenIterable: Iterable<*>,
947 - expirationTime: ExpirationTimeOpaque,
889 + lanes: Lanes,
890 ): Fiber | null {
891 // This is the same implementation as reconcileChildrenArray(),
892 // but using the iterator instead.
@@ -1023,12 +965,7 @@ function ChildReconciler(shouldTrackSideEffects) {
965 } else {
966 nextOldFiber = oldFiber.sibling;
967 }
1026 - const newFiber = updateSlot(
1027 - returnFiber,
1028 - oldFiber,
1029 - step.value,
1030 - expirationTime,
1031 - );
968 + const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
969 if (newFiber === null) {
970 // TODO: This breaks on empty slots like null children. That's
971 // unfortunate because it triggers the slow path all the time. We need
@@ -1071,7 +1008,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1008 // If we don't have any more existing children we can choose a fast path
1009 // since the rest will all be insertions.
1010 for (; !step.done; newIdx++, step = newChildren.next()) {
1074 - const newFiber = createChild(returnFiber, step.value, expirationTime);
1011 + const newFiber = createChild(returnFiber, step.value, lanes);
1012 if (newFiber === null) {
1013 continue;
1014 }
@@ -1097,7 +1034,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1034 returnFiber,
1035 newIdx,
1036 step.value,
1100 - expirationTime,
1037 + lanes,
1038 );
1039 if (newFiber !== null) {
1040 if (shouldTrackSideEffects) {
@@ -1134,7 +1071,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1071 returnFiber: Fiber,
1072 currentFirstChild: Fiber | null,
1073 textContent: string,
1137 - expirationTime: ExpirationTimeOpaque,
1074 + lanes: Lanes,
1075 ): Fiber {
1076 // There's no need to check for keys on text nodes since we don't have a
1077 // way to define them.
@@ -1149,11 +1086,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1086 // The existing first child is not a text node so we need to create one
1087 // and delete the existing ones.
1088 deleteRemainingChildren(returnFiber, currentFirstChild);
1152 - const created = createFiberFromText(
1153 - textContent,
1154 - returnFiber.mode,
1155 - expirationTime,
1156 - );
1089 + const created = createFiberFromText(textContent, returnFiber.mode, lanes);
1090 created.return = returnFiber;
1091 return created;
1092 }
@@ -1162,7 +1095,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1095 returnFiber: Fiber,
1096 currentFirstChild: Fiber | null,
1097 element: ReactElement,
1165 - expirationTime: ExpirationTimeOpaque,
1098 + lanes: Lanes,
1099 ): Fiber {
1100 const key = element.key;
1101 let child = currentFirstChild;
@@ -1245,17 +1178,13 @@ function ChildReconciler(shouldTrackSideEffects) {
1178 const created = createFiberFromFragment(
1179 element.props.children,
1180 returnFiber.mode,
1248 - expirationTime,
1181 + lanes,
1182 element.key,
1183 );
1184 created.return = returnFiber;
1185 return created;
1186 } else {
1254 - const created = createFiberFromElement(
1255 - element,
1256 - returnFiber.mode,
1257 - expirationTime,
1258 - );
1187 + const created = createFiberFromElement(element, returnFiber.mode, lanes);
1188 created.ref = coerceRef(returnFiber, currentFirstChild, element);
1189 created.return = returnFiber;
1190 return created;
@@ -1266,7 +1195,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1195 returnFiber: Fiber,
1196 currentFirstChild: Fiber | null,
1197 portal: ReactPortal,
1269 - expirationTime: ExpirationTimeOpaque,
1198 + lanes: Lanes,
1199 ): Fiber {
1200 const key = portal.key;
1201 let child = currentFirstChild;
@@ -1293,11 +1222,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1222 child = child.sibling;
1223 }
1224
1296 - const created = createFiberFromPortal(
1297 - portal,
1298 - returnFiber.mode,
1299 - expirationTime,
1300 - );
1225 + const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
1226 created.return = returnFiber;
1227 return created;
1228 }
@@ -1309,7 +1234,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1234 returnFiber: Fiber,
1235 currentFirstChild: Fiber | null,
1236 newChild: any,
1312 - expirationTime: ExpirationTimeOpaque,
1237 + lanes: Lanes,
1238 ): Fiber | null {
1239 // This function is not recursive.
1240 // If the top level item is an array, we treat it as a set of children,
@@ -1339,7 +1264,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1264 returnFiber,
1265 currentFirstChild,
1266 newChild,
1342 - expirationTime,
1267 + lanes,
1268 ),
1269 );
1270 case REACT_PORTAL_TYPE:
@@ -1348,7 +1273,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1273 returnFiber,
1274 currentFirstChild,
1275 newChild,
1351 - expirationTime,
1276 + lanes,
1277 ),
1278 );
1279 }
@@ -1360,7 +1285,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1285 returnFiber,
1286 currentFirstChild,
1287 '' + newChild,
1363 - expirationTime,
1288 + lanes,
1289 ),
1290 );
1291 }
@@ -1370,7 +1295,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1295 returnFiber,
1296 currentFirstChild,
1297 newChild,
1373 - expirationTime,
1298 + lanes,
1299 );
1300 }
1301
@@ -1379,7 +1304,7 @@ function ChildReconciler(shouldTrackSideEffects) {
1304 returnFiber,
1305 currentFirstChild,
1306 newChild,
1382 - expirationTime,
1307 + lanes,
1308 );
1309 }
1310
@@ -1462,13 +1387,10 @@ export function cloneChildFibers(
1387 }
1388
1389 // Reset a workInProgress child set to prepare it for a second pass.
1465 -export function resetChildFibers(
1466 - workInProgress: Fiber,
1467 - renderExpirationTime: ExpirationTimeOpaque,
1468 -): void {
1390 +export function resetChildFibers(workInProgress: Fiber, lanes: Lanes): void {
1391 let child = workInProgress.child;
1392 while (child !== null) {
1471 - resetWorkInProgress(child, renderExpirationTime);
1393 + resetWorkInProgress(child, lanes);
1394 child = child.sibling;
1395 }
1396 }
packages/react-reconciler/src/ReactFiber.new.js
+48 -72
@@ -18,7 +18,7 @@ import type {Fiber} from './ReactInternalTypes';
18 import type {RootTag} from './ReactRootTags';
19 import type {WorkTag} from './ReactWorkTags';
20 import type {TypeOfMode} from './ReactTypeOfMode';
21 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
21 +import type {Lanes} from './ReactFiberLane';
22 import type {SuspenseInstance} from './ReactFiberHostConfig';
23 import type {OffscreenProps} from './ReactFiberOffscreenComponent';
24
@@ -66,7 +66,7 @@ import {
66 resolveFunctionForHotReloading,
67 resolveForwardRefForHotReloading,
68 } from './ReactFiberHotReloading.new';
69 -import {NoWork} from './ReactFiberExpirationTime.new';
69 +import {NoLanes} from './ReactFiberLane';
70 import {
71 NoMode,
72 ConcurrentMode,
@@ -150,8 +150,8 @@ function FiberNode(
150 this.firstEffect = null;
151 this.lastEffect = null;
152
153 - this.expirationTime_opaque = NoWork;
154 - this.childExpirationTime_opaque = NoWork;
153 + this.lanes = NoLanes;
154 + this.childLanes = NoLanes;
155
156 this.alternate = null;
157
@@ -314,9 +314,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
314 }
315 }
316
317 - workInProgress.childExpirationTime_opaque =
318 - current.childExpirationTime_opaque;
319 - workInProgress.expirationTime_opaque = current.expirationTime_opaque;
317 + workInProgress.childLanes = current.childLanes;
318 + workInProgress.lanes = current.lanes;
319
320 workInProgress.child = current.child;
321 workInProgress.memoizedProps = current.memoizedProps;
@@ -330,7 +329,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
329 currentDependencies === null
330 ? null
331 : {
333 - expirationTime: currentDependencies.expirationTime,
332 + lanes: currentDependencies.lanes,
333 firstContext: currentDependencies.firstContext,
334 responders: currentDependencies.responders,
335 };
@@ -368,10 +367,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
367 }
368
369 // Used to reuse a Fiber for a second pass.
371 -export function resetWorkInProgress(
372 - workInProgress: Fiber,
373 - renderExpirationTime: ExpirationTimeOpaque,
374 -) {
370 +export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
371 // This resets the Fiber to what createFiber or createWorkInProgress would
372 // have set the values to before during the first pass. Ideally this wouldn't
373 // be necessary but unfortunately many code paths reads from the workInProgress
@@ -392,8 +388,8 @@ export function resetWorkInProgress(
388 const current = workInProgress.alternate;
389 if (current === null) {
390 // Reset to createFiber's initial values.
395 - workInProgress.childExpirationTime_opaque = NoWork;
396 - workInProgress.expirationTime_opaque = renderExpirationTime;
391 + workInProgress.childLanes = NoLanes;
392 + workInProgress.lanes = renderLanes;
393
394 workInProgress.child = null;
395 workInProgress.memoizedProps = null;
@@ -412,9 +408,8 @@ export function resetWorkInProgress(
408 }
409 } else {
410 // Reset to the cloned values that createWorkInProgress would've.
415 - workInProgress.childExpirationTime_opaque =
416 - current.childExpirationTime_opaque;
417 - workInProgress.expirationTime_opaque = current.expirationTime_opaque;
411 + workInProgress.childLanes = current.childLanes;
412 + workInProgress.lanes = current.lanes;
413
414 workInProgress.child = current.child;
415 workInProgress.memoizedProps = current.memoizedProps;
@@ -428,7 +423,7 @@ export function resetWorkInProgress(
423 currentDependencies === null
424 ? null
425 : {
431 - expirationTime: currentDependencies.expirationTime,
426 + lanes: currentDependencies.lanes,
427 firstContext: currentDependencies.firstContext,
428 responders: currentDependencies.responders,
429 };
@@ -470,7 +465,7 @@ export function createFiberFromTypeAndProps(
465 pendingProps: any,
466 owner: null | Fiber,
467 mode: TypeOfMode,
473 - expirationTime: ExpirationTimeOpaque,
468 + lanes: Lanes,
469 ): Fiber {
470 let fiberTag = IndeterminateComponent;
471 // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
@@ -491,12 +486,7 @@ export function createFiberFromTypeAndProps(
486 } else {
487 getTag: switch (type) {
488 case REACT_FRAGMENT_TYPE:
494 - return createFiberFromFragment(
495 - pendingProps.children,
496 - mode,
497 - expirationTime,
498 - key,
499 - );
489 + return createFiberFromFragment(pendingProps.children, mode, lanes, key);
490 case REACT_DEBUG_TRACING_MODE_TYPE:
491 fiberTag = Mode;
492 mode |= DebugTracingMode;
@@ -506,30 +496,16 @@ export function createFiberFromTypeAndProps(
496 mode |= StrictMode;
497 break;
498 case REACT_PROFILER_TYPE:
509 - return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
499 + return createFiberFromProfiler(pendingProps, mode, lanes, key);
500 case REACT_SUSPENSE_TYPE:
511 - return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
501 + return createFiberFromSuspense(pendingProps, mode, lanes, key);
502 case REACT_SUSPENSE_LIST_TYPE:
513 - return createFiberFromSuspenseList(
514 - pendingProps,
515 - mode,
516 - expirationTime,
517 - key,
518 - );
503 + return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
504 case REACT_OFFSCREEN_TYPE:
520 - return createFiberFromOffscreen(
521 - pendingProps,
522 - mode,
523 - expirationTime,
524 - key,
525 - );
505 + return createFiberFromOffscreen(pendingProps, mode, lanes, key);
506 case REACT_LEGACY_HIDDEN_TYPE:
527 - return createFiberFromLegacyHidden(
528 - pendingProps,
529 - mode,
530 - expirationTime,
531 - key,
532 - );
507 + return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
508 +
509 default: {
510 if (typeof type === 'object' && type !== null) {
511 switch (type.$$typeof) {
@@ -562,7 +538,7 @@ export function createFiberFromTypeAndProps(
538 type,
539 pendingProps,
540 mode,
565 - expirationTime,
541 + lanes,
542 key,
543 );
544 }
@@ -573,7 +549,7 @@ export function createFiberFromTypeAndProps(
549 type,
550 pendingProps,
551 mode,
576 - expirationTime,
552 + lanes,
553 key,
554 );
555 }
@@ -612,7 +588,7 @@ export function createFiberFromTypeAndProps(
588 const fiber = createFiber(fiberTag, pendingProps, key, mode);
589 fiber.elementType = type;
590 fiber.type = resolvedType;
615 - fiber.expirationTime_opaque = expirationTime;
591 + fiber.lanes = lanes;
592
593 return fiber;
594 }
@@ -620,7 +596,7 @@ export function createFiberFromTypeAndProps(
596 export function createFiberFromElement(
597 element: ReactElement,
598 mode: TypeOfMode,
623 - expirationTime: ExpirationTimeOpaque,
599 + lanes: Lanes,
600 ): Fiber {
601 let owner = null;
602 if (__DEV__) {
@@ -635,7 +611,7 @@ export function createFiberFromElement(
611 pendingProps,
612 owner,
613 mode,
638 - expirationTime,
614 + lanes,
615 );
616 if (__DEV__) {
617 fiber._debugSource = element._source;
@@ -647,11 +623,11 @@ export function createFiberFromElement(
623 export function createFiberFromFragment(
624 elements: ReactFragment,
625 mode: TypeOfMode,
650 - expirationTime: ExpirationTimeOpaque,
626 + lanes: Lanes,
627 key: null | string,
628 ): Fiber {
629 const fiber = createFiber(Fragment, elements, key, mode);
654 - fiber.expirationTime_opaque = expirationTime;
630 + fiber.lanes = lanes;
631 return fiber;
632 }
633
@@ -659,13 +635,13 @@ export function createFiberFromFundamental(
635 fundamentalComponent: ReactFundamentalComponent<any, any>,
636 pendingProps: any,
637 mode: TypeOfMode,
662 - expirationTime: ExpirationTimeOpaque,
638 + lanes: Lanes,
639 key: null | string,
640 ): Fiber {
641 const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
642 fiber.elementType = fundamentalComponent;
643 fiber.type = fundamentalComponent;
668 - fiber.expirationTime_opaque = expirationTime;
644 + fiber.lanes = lanes;
645 return fiber;
646 }
647
@@ -673,20 +649,20 @@ function createFiberFromScope(
649 scope: ReactScope,
650 pendingProps: any,
651 mode: TypeOfMode,
676 - expirationTime: ExpirationTimeOpaque,
652 + lanes: Lanes,
653 key: null | string,
654 ) {
655 const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
656 fiber.type = scope;
657 fiber.elementType = scope;
682 - fiber.expirationTime_opaque = expirationTime;
658 + fiber.lanes = lanes;
659 return fiber;
660 }
661
662 function createFiberFromProfiler(
663 pendingProps: any,
664 mode: TypeOfMode,
689 - expirationTime: ExpirationTimeOpaque,
665 + lanes: Lanes,
666 key: null | string,
667 ): Fiber {
668 if (__DEV__) {
@@ -699,7 +675,7 @@ function createFiberFromProfiler(
675 // TODO: The Profiler fiber shouldn't have a type. It has a tag.
676 fiber.elementType = REACT_PROFILER_TYPE;
677 fiber.type = REACT_PROFILER_TYPE;
702 - fiber.expirationTime_opaque = expirationTime;
678 + fiber.lanes = lanes;
679
680 if (enableProfilerTimer) {
681 fiber.stateNode = {
@@ -714,7 +690,7 @@ function createFiberFromProfiler(
690 export function createFiberFromSuspense(
691 pendingProps: any,
692 mode: TypeOfMode,
717 - expirationTime: ExpirationTimeOpaque,
693 + lanes: Lanes,
694 key: null | string,
695 ) {
696 const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
@@ -725,14 +701,14 @@ export function createFiberFromSuspense(
701 fiber.type = REACT_SUSPENSE_TYPE;
702 fiber.elementType = REACT_SUSPENSE_TYPE;
703
728 - fiber.expirationTime_opaque = expirationTime;
704 + fiber.lanes = lanes;
705 return fiber;
706 }
707
708 export function createFiberFromSuspenseList(
709 pendingProps: any,
710 mode: TypeOfMode,
735 - expirationTime: ExpirationTimeOpaque,
711 + lanes: Lanes,
712 key: null | string,
713 ) {
714 const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
@@ -743,14 +719,14 @@ export function createFiberFromSuspenseList(
719 fiber.type = REACT_SUSPENSE_LIST_TYPE;
720 }
721 fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
746 - fiber.expirationTime_opaque = expirationTime;
722 + fiber.lanes = lanes;
723 return fiber;
724 }
725
726 export function createFiberFromOffscreen(
727 pendingProps: OffscreenProps,
728 mode: TypeOfMode,
753 - expirationTime: ExpirationTimeOpaque,
729 + lanes: Lanes,
730 key: null | string,
731 ) {
732 const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
@@ -761,14 +737,14 @@ export function createFiberFromOffscreen(
737 fiber.type = REACT_OFFSCREEN_TYPE;
738 }
739 fiber.elementType = REACT_OFFSCREEN_TYPE;
764 - fiber.expirationTime_opaque = expirationTime;
740 + fiber.lanes = lanes;
741 return fiber;
742 }
743
744 export function createFiberFromLegacyHidden(
745 pendingProps: OffscreenProps,
746 mode: TypeOfMode,
771 - expirationTime: ExpirationTimeOpaque,
747 + lanes: Lanes,
748 key: null | string,
749 ) {
750 const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
@@ -779,17 +755,17 @@ export function createFiberFromLegacyHidden(
755 fiber.type = REACT_LEGACY_HIDDEN_TYPE;
756 }
757 fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
782 - fiber.expirationTime_opaque = expirationTime;
758 + fiber.lanes = lanes;
759 return fiber;
760 }
761
762 export function createFiberFromText(
763 content: string,
764 mode: TypeOfMode,
789 - expirationTime: ExpirationTimeOpaque,
765 + lanes: Lanes,
766 ): Fiber {
767 const fiber = createFiber(HostText, content, null, mode);
792 - fiber.expirationTime_opaque = expirationTime;
768 + fiber.lanes = lanes;
769 return fiber;
770 }
771
@@ -812,11 +788,11 @@ export function createFiberFromDehydratedFragment(
788 export function createFiberFromPortal(
789 portal: ReactPortal,
790 mode: TypeOfMode,
815 - expirationTime: ExpirationTimeOpaque,
791 + lanes: Lanes,
792 ): Fiber {
793 const pendingProps = portal.children !== null ? portal.children : [];
794 const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
819 - fiber.expirationTime_opaque = expirationTime;
795 + fiber.lanes = lanes;
796 fiber.stateNode = {
797 containerInfo: portal.containerInfo,
798 pendingChildren: null, // Used by persistent updates
@@ -862,8 +838,8 @@ export function assignFiberPropertiesInDEV(
838 target.nextEffect = source.nextEffect;
839 target.firstEffect = source.firstEffect;
840 target.lastEffect = source.lastEffect;
865 - target.expirationTime_opaque = source.expirationTime_opaque;
866 - target.childExpirationTime_opaque = source.childExpirationTime_opaque;
841 + target.lanes = source.lanes;
842 + target.childLanes = source.childLanes;
843 target.alternate = source.alternate;
844 if (enableProfilerTimer) {
845 target.actualDuration = source.actualDuration;
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+277 -535
@@ -12,7 +12,7 @@ import type {BlockComponent} from 'react/src/ReactBlock';
12 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
13 import type {Fiber} from './ReactInternalTypes';
14 import type {FiberRoot} from './ReactInternalTypes';
15 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
15 +import type {Lanes, Lane} from './ReactFiberLane';
16 import type {
17 SuspenseState,
18 SuspenseListRenderState,
@@ -107,14 +107,17 @@ import {
107 initializeUpdateQueue,
108 } from './ReactUpdateQueue.new';
109 import {
110 - NoWork,
111 - Never,
112 - Sync,
113 - DefaultUpdateTime,
114 - isSameOrHigherPriority,
115 - isSameExpirationTime,
116 - bumpPriorityHigher,
117 -} from './ReactFiberExpirationTime.new';
110 + NoLane,
111 + NoLanes,
112 + SyncLane,
113 + OffscreenLane,
114 + DefaultHydrationLane,
115 + includesSomeLane,
116 + laneToLanes,
117 + removeLanes,
118 + mergeLanes,
119 + getBumpedLaneForHydration,
120 +} from './ReactFiberLane';
121 import {
122 ConcurrentMode,
123 NoMode,
@@ -191,9 +194,9 @@ import {
194 retryDehydratedSuspenseBoundary,
195 scheduleUpdateOnFiber,
196 renderDidSuspendDelayIfPossible,
194 - markUnprocessedUpdateTime,
197 + markSkippedUpdateLanes,
198 getWorkInProgressRoot,
196 - pushRenderExpirationTime,
199 + pushRenderLanes,
200 } from './ReactFiberWorkLoop.new';
201
202 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
@@ -228,7 +231,7 @@ export function reconcileChildren(
231 current: Fiber | null,
232 workInProgress: Fiber,
233 nextChildren: any,
231 - renderExpirationTime: ExpirationTimeOpaque,
234 + renderLanes: Lanes,
235 ) {
236 if (current === null) {
237 // If this is a fresh new component that hasn't been rendered yet, we
@@ -239,7 +242,7 @@ export function reconcileChildren(
242 workInProgress,
243 null,
244 nextChildren,
242 - renderExpirationTime,
245 + renderLanes,
246 );
247 } else {
248 // If the current child is the same as the work in progress, it means that
@@ -252,7 +255,7 @@ export function reconcileChildren(
255 workInProgress,
256 current.child,
257 nextChildren,
255 - renderExpirationTime,
258 + renderLanes,
259 );
260 }
261 }
@@ -261,7 +264,7 @@ function forceUnmountCurrentAndReconcile(
264 current: Fiber,
265 workInProgress: Fiber,
266 nextChildren: any,
264 - renderExpirationTime: ExpirationTimeOpaque,
267 + renderLanes: Lanes,
268 ) {
269 // This function is fork of reconcileChildren. It's used in cases where we
270 // want to reconcile without matching against the existing set. This has the
@@ -275,7 +278,7 @@ function forceUnmountCurrentAndReconcile(
278 workInProgress,
279 current.child,
280 null,
278 - renderExpirationTime,
281 + renderLanes,
282 );
283 // In the second pass, we mount the new children. The trick here is that we
284 // pass null in place of where we usually pass the current child set. This has
@@ -285,7 +288,7 @@ function forceUnmountCurrentAndReconcile(
288 workInProgress,
289 null,
290 nextChildren,
288 - renderExpirationTime,
291 + renderLanes,
292 );
293 }
294
@@ -294,7 +297,7 @@ function updateForwardRef(
297 workInProgress: Fiber,
298 Component: any,
299 nextProps: any,
297 - renderExpirationTime: ExpirationTimeOpaque,
300 + renderLanes: Lanes,
301 ) {
302 // TODO: current can be non-null here even if the component
303 // hasn't yet mounted. This happens after the first render suspends.
@@ -321,7 +324,7 @@ function updateForwardRef(
324
325 // The rest is a fork of updateFunctionComponent
326 let nextChildren;
324 - prepareToReadContext(workInProgress, renderExpirationTime);
327 + prepareToReadContext(workInProgress, renderLanes);
328 if (__DEV__) {
329 ReactCurrentOwner.current = workInProgress;
330 setIsRendering(true);
@@ -331,7 +334,7 @@ function updateForwardRef(
334 render,
335 nextProps,
336 ref,
334 - renderExpirationTime,
337 + renderLanes,
338 );
339 if (
340 debugRenderPhaseSideEffectsForStrictMode &&
@@ -345,7 +348,7 @@ function updateForwardRef(
348 render,
349 nextProps,
350 ref,
348 - renderExpirationTime,
351 + renderLanes,
352 );
353 } finally {
354 reenableLogs();
@@ -359,27 +362,18 @@ function updateForwardRef(
362 render,
363 nextProps,
364 ref,
362 - renderExpirationTime,
365 + renderLanes,
366 );
367 }
368
369 if (current !== null && !didReceiveUpdate) {
367 - bailoutHooks(current, workInProgress, renderExpirationTime);
368 - return bailoutOnAlreadyFinishedWork(
369 - current,
370 - workInProgress,
371 - renderExpirationTime,
372 - );
370 + bailoutHooks(current, workInProgress, renderLanes);
371 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
372 }
373
374 // React DevTools reads this flag.
375 workInProgress.effectTag |= PerformedWork;
377 - reconcileChildren(
378 - current,
379 - workInProgress,
380 - nextChildren,
381 - renderExpirationTime,
382 - );
376 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
377 return workInProgress.child;
378 }
379
@@ -388,8 +382,8 @@ function updateMemoComponent(
382 workInProgress: Fiber,
383 Component: any,
384 nextProps: any,
391 - updateExpirationTime,
392 - renderExpirationTime: ExpirationTimeOpaque,
385 + updateLanes: Lanes,
386 + renderLanes: Lanes,
387 ): null | Fiber {
388 if (current === null) {
389 const type = Component.type;
@@ -416,8 +410,8 @@ function updateMemoComponent(
410 workInProgress,
411 resolvedType,
412 nextProps,
419 - updateExpirationTime,
420 - renderExpirationTime,
413 + updateLanes,
414 + renderLanes,
415 );
416 }
417 if (__DEV__) {
@@ -439,7 +433,7 @@ function updateMemoComponent(
433 nextProps,
434 null,
435 workInProgress.mode,
442 - renderExpirationTime,
436 + renderLanes,
437 );
438 child.ref = workInProgress.ref;
439 child.return = workInProgress;
@@ -461,7 +455,7 @@ function updateMemoComponent(
455 }
456 }
457 const currentChild = ((current.child: any): Fiber); // This is always exactly one child
464 - if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
458 + if (!includesSomeLane(updateLanes, renderLanes)) {
459 // This will be the props with resolved defaultProps,
460 // unlike current.memoizedProps which will be the unresolved ones.
461 const prevProps = currentChild.memoizedProps;
@@ -469,11 +463,7 @@ function updateMemoComponent(
463 let compare = Component.compare;
464 compare = compare !== null ? compare : shallowEqual;
465 if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
472 - return bailoutOnAlreadyFinishedWork(
473 - current,
474 - workInProgress,
475 - renderExpirationTime,
476 - );
466 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
467 }
468 }
469 // React DevTools reads this flag.
@@ -490,8 +480,8 @@ function updateSimpleMemoComponent(
480 workInProgress: Fiber,
481 Component: any,
482 nextProps: any,
493 - updateExpirationTime,
494 - renderExpirationTime: ExpirationTimeOpaque,
483 + updateLanes: Lanes,
484 + renderLanes: Lanes,
485 ): null | Fiber {
486 // TODO: current can be non-null here even if the component
487 // hasn't yet mounted. This happens when the inner render suspends.
@@ -536,10 +526,10 @@ function updateSimpleMemoComponent(
526 (__DEV__ ? workInProgress.type === current.type : true)
527 ) {
528 didReceiveUpdate = false;
539 - if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
540 - // The pending update priority was cleared at the beginning of
541 - // beginWork. We're about to bail out, but there might be additional
542 - // updates at a lower priority. Usually, the priority level of the
529 + if (!includesSomeLane(renderLanes, updateLanes)) {
530 + // The pending lanes were cleared at the beginning of beginWork. We're
531 + // about to bail out, but there might be other lanes that weren't
532 + // included in the current render. Usually, the priority level of the
533 // remaining updates is accumlated during the evaluation of the
534 // component (i.e. when processing the update queue). But since since
535 // we're bailing out early *without* evaluating the component, we need
@@ -550,11 +540,11 @@ function updateSimpleMemoComponent(
540 // contains hooks.
541 // TODO: Move the reset at in beginWork out of the common path so that
542 // this is no longer necessary.
553 - workInProgress.expirationTime_opaque = current.expirationTime_opaque;
543 + workInProgress.lanes = current.lanes;
544 return bailoutOnAlreadyFinishedWork(
545 current,
546 workInProgress,
557 - renderExpirationTime,
547 + renderLanes,
548 );
549 }
550 }
@@ -564,14 +554,14 @@ function updateSimpleMemoComponent(
554 workInProgress,
555 Component,
556 nextProps,
567 - renderExpirationTime,
557 + renderLanes,
558 );
559 }
560
561 function updateOffscreenComponent(
562 current: Fiber | null,
563 workInProgress: Fiber,
574 - renderExpirationTime: ExpirationTimeOpaque,
564 + renderLanes: Lanes,
565 ) {
566 const nextProps: OffscreenProps = workInProgress.pendingProps;
567 const nextChildren = nextProps.children;
@@ -580,71 +570,57 @@ function updateOffscreenComponent(
570 current !== null ? current.memoizedState : null;
571
572 if (nextProps.mode === 'hidden') {
583 - if (
584 - !isSameExpirationTime(renderExpirationTime, (Never: ExpirationTimeOpaque))
585 - ) {
586 - let nextBaseTime;
573 + if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
574 + let nextBaseLanes;
575 if (prevState !== null) {
588 - const prevBaseTime = prevState.baseTime;
589 - nextBaseTime = !isSameOrHigherPriority(
590 - prevBaseTime,
591 - renderExpirationTime,
592 - )
593 - ? prevBaseTime
594 - : renderExpirationTime;
576 + const prevBaseLanes = prevState.baseLanes;
577 + nextBaseLanes = mergeLanes(prevBaseLanes, renderLanes);
578 } else {
596 - nextBaseTime = renderExpirationTime;
579 + nextBaseLanes = renderLanes;
580 }
581
582 // Schedule this fiber to re-render at offscreen priority. Then bailout.
583 if (enableSchedulerTracing) {
601 - markSpawnedWork((Never: ExpirationTimeOpaque));
584 + markSpawnedWork((OffscreenLane: Lane));
585 }
603 - workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
586 + workInProgress.lanes = workInProgress.childLanes = laneToLanes(
587 + OffscreenLane,
588 + );
589 const nextState: OffscreenState = {
605 - baseTime: nextBaseTime,
590 + baseLanes: nextBaseLanes,
591 };
592 workInProgress.memoizedState = nextState;
593 // We're about to bail out, but we need to push this to the stack anyway
594 // to avoid a push/pop misalignment.
610 - pushRenderExpirationTime(workInProgress, nextBaseTime);
595 + pushRenderLanes(workInProgress, nextBaseLanes);
596 return null;
597 } else {
613 - // Rendering at offscreen, so we can clear the base time.
598 + // Rendering at offscreen, so we can clear the base lanes.
599 const nextState: OffscreenState = {
615 - baseTime: NoWork,
600 + baseLanes: NoLanes,
601 };
602 workInProgress.memoizedState = nextState;
618 - pushRenderExpirationTime(workInProgress, renderExpirationTime);
603 + // Push the lanes that were skipped when we bailed out.
604 + const subtreeRenderLanes =
605 + prevState !== null ? prevState.baseLanes : renderLanes;
606 + pushRenderLanes(workInProgress, subtreeRenderLanes);
607 }
608 } else {
621 - let subtreeRenderTime;
609 + let subtreeRenderLanes;
610 if (prevState !== null) {
623 - const baseTime = prevState.baseTime;
624 - subtreeRenderTime = !isSameOrHigherPriority(
625 - baseTime,
626 - renderExpirationTime,
627 - )
628 - ? baseTime
629 - : renderExpirationTime;
630 -
611 + subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes);
612 // Since we're not hidden anymore, reset the state
613 workInProgress.memoizedState = null;
614 } else {
615 // We weren't previously hidden, and we still aren't, so there's nothing
616 // special to do. Need to push to the stack regardless, though, to avoid
617 // a push/pop misalignment.
637 - subtreeRenderTime = renderExpirationTime;
618 + subtreeRenderLanes = renderLanes;
619 }
639 - pushRenderExpirationTime(workInProgress, subtreeRenderTime);
620 + pushRenderLanes(workInProgress, subtreeRenderLanes);
621 }
622
642 - reconcileChildren(
643 - current,
644 - workInProgress,
645 - nextChildren,
646 - renderExpirationTime,
647 - );
623 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
624 return workInProgress.child;
625 }
626
@@ -656,37 +632,27 @@ const updateLegacyHiddenComponent = updateOffscreenComponent;
632 function updateFragment(
633 current: Fiber | null,
634 workInProgress: Fiber,
659 - renderExpirationTime: ExpirationTimeOpaque,
635 + renderLanes: Lanes,
636 ) {
637 const nextChildren = workInProgress.pendingProps;
662 - reconcileChildren(
663 - current,
664 - workInProgress,
665 - nextChildren,
666 - renderExpirationTime,
667 - );
638 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
639 return workInProgress.child;
640 }
641
642 function updateMode(
643 current: Fiber | null,
644 workInProgress: Fiber,
674 - renderExpirationTime: ExpirationTimeOpaque,
645 + renderLanes: Lanes,
646 ) {
647 const nextChildren = workInProgress.pendingProps.children;
677 - reconcileChildren(
678 - current,
679 - workInProgress,
680 - nextChildren,
681 - renderExpirationTime,
682 - );
648 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
649 return workInProgress.child;
650 }
651
652 function updateProfiler(
653 current: Fiber | null,
654 workInProgress: Fiber,
689 - renderExpirationTime: ExpirationTimeOpaque,
655 + renderLanes: Lanes,
656 ) {
657 if (enableProfilerTimer) {
658 workInProgress.effectTag |= Update;
@@ -699,12 +665,7 @@ function updateProfiler(
665 }
666 const nextProps = workInProgress.pendingProps;
667 const nextChildren = nextProps.children;
702 - reconcileChildren(
703 - current,
704 - workInProgress,
705 - nextChildren,
706 - renderExpirationTime,
707 - );
668 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
669 return workInProgress.child;
670 }
671
@@ -724,7 +685,7 @@ function updateFunctionComponent(
685 workInProgress,
686 Component,
687 nextProps: any,
727 - renderExpirationTime,
688 + renderLanes,
689 ) {
690 if (__DEV__) {
691 if (workInProgress.type !== workInProgress.elementType) {
@@ -749,7 +710,7 @@ function updateFunctionComponent(
710 }
711
712 let nextChildren;
752 - prepareToReadContext(workInProgress, renderExpirationTime);
713 + prepareToReadContext(workInProgress, renderLanes);
714 if (__DEV__) {
715 ReactCurrentOwner.current = workInProgress;
716 setIsRendering(true);
@@ -759,7 +720,7 @@ function updateFunctionComponent(
720 Component,
721 nextProps,
722 context,
762 - renderExpirationTime,
723 + renderLanes,
724 );
725 if (
726 debugRenderPhaseSideEffectsForStrictMode &&
@@ -773,7 +734,7 @@ function updateFunctionComponent(
734 Component,
735 nextProps,
736 context,
776 - renderExpirationTime,
737 + renderLanes,
738 );
739 } finally {
740 reenableLogs();
@@ -787,27 +748,18 @@ function updateFunctionComponent(
748 Component,
749 nextProps,
750 context,
790 - renderExpirationTime,
751 + renderLanes,
752 );
753 }
754
755 if (current !== null && !didReceiveUpdate) {
795 - bailoutHooks(current, workInProgress, renderExpirationTime);
796 - return bailoutOnAlreadyFinishedWork(
797 - current,
798 - workInProgress,
799 - renderExpirationTime,
800 - );
756 + bailoutHooks(current, workInProgress, renderLanes);
757 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
758 }
759
760 // React DevTools reads this flag.
761 workInProgress.effectTag |= PerformedWork;
805 - reconcileChildren(
806 - current,
807 - workInProgress,
808 - nextChildren,
809 - renderExpirationTime,
810 - );
762 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
763 return workInProgress.child;
764 }
765
@@ -816,7 +768,7 @@ function updateBlock<Props, Data>(
768 workInProgress: Fiber,
769 block: BlockComponent<Props, Data>,
770 nextProps: any,
819 - renderExpirationTime: ExpirationTimeOpaque,
771 + renderLanes: Lanes,
772 ) {
773 // TODO: current can be non-null here even if the component
774 // hasn't yet mounted. This happens after the first render suspends.
@@ -827,7 +779,7 @@ function updateBlock<Props, Data>(
779
780 // The rest is a fork of updateFunctionComponent
781 let nextChildren;
830 - prepareToReadContext(workInProgress, renderExpirationTime);
782 + prepareToReadContext(workInProgress, renderLanes);
783 if (__DEV__) {
784 ReactCurrentOwner.current = workInProgress;
785 setIsRendering(true);
@@ -837,7 +789,7 @@ function updateBlock<Props, Data>(
789 render,
790 nextProps,
791 data,
840 - renderExpirationTime,
792 + renderLanes,
793 );
794 if (
795 debugRenderPhaseSideEffectsForStrictMode &&
@@ -851,7 +803,7 @@ function updateBlock<Props, Data>(
803 render,
804 nextProps,
805 data,
854 - renderExpirationTime,
806 + renderLanes,
807 );
808 } finally {
809 reenableLogs();
@@ -865,27 +817,18 @@ function updateBlock<Props, Data>(
817 render,
818 nextProps,
819 data,
868 - renderExpirationTime,
820 + renderLanes,
821 );
822 }
823
824 if (current !== null && !didReceiveUpdate) {
873 - bailoutHooks(current, workInProgress, renderExpirationTime);
874 - return bailoutOnAlreadyFinishedWork(
875 - current,
876 - workInProgress,
877 - renderExpirationTime,
878 - );
825 + bailoutHooks(current, workInProgress, renderLanes);
826 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
827 }
828
829 // React DevTools reads this flag.
830 workInProgress.effectTag |= PerformedWork;
883 - reconcileChildren(
884 - current,
885 - workInProgress,
886 - nextChildren,
887 - renderExpirationTime,
888 - );
831 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
832 return workInProgress.child;
833 }
834
@@ -893,8 +836,8 @@ function updateClassComponent(
836 current: Fiber | null,
837 workInProgress: Fiber,
838 Component: any,
896 - nextProps,
897 - renderExpirationTime: ExpirationTimeOpaque,
839 + nextProps: any,
840 + renderLanes: Lanes,
841 ) {
842 if (__DEV__) {
843 if (workInProgress.type !== workInProgress.elementType) {
@@ -922,7 +865,7 @@ function updateClassComponent(
865 } else {
866 hasContext = false;
867 }
925 - prepareToReadContext(workInProgress, renderExpirationTime);
868 + prepareToReadContext(workInProgress, renderLanes);
869
870 const instance = workInProgress.stateNode;
871 let shouldUpdate;
@@ -939,12 +882,7 @@ function updateClassComponent(
882 }
883 // In the initial pass we might need to construct the instance.
884 constructClassInstance(workInProgress, Component, nextProps);
942 - mountClassInstance(
943 - workInProgress,
944 - Component,
945 - nextProps,
946 - renderExpirationTime,
947 - );
885 + mountClassInstance(workInProgress, Component, nextProps, renderLanes);
886 shouldUpdate = true;
887 } else if (current === null) {
888 // In a resume, we'll already have an instance we can reuse.
@@ -952,7 +890,7 @@ function updateClassComponent(
890 workInProgress,
891 Component,
892 nextProps,
955 - renderExpirationTime,
893 + renderLanes,
894 );
895 } else {
896 shouldUpdate = updateClassInstance(
@@ -960,7 +898,7 @@ function updateClassComponent(
898 workInProgress,
899 Component,
900 nextProps,
963 - renderExpirationTime,
901 + renderLanes,
902 );
903 }
904 const nextUnitOfWork = finishClassComponent(
@@ -969,7 +907,7 @@ function updateClassComponent(
907 Component,
908 shouldUpdate,
909 hasContext,
972 - renderExpirationTime,
910 + renderLanes,
911 );
912 if (__DEV__) {
913 const inst = workInProgress.stateNode;
@@ -993,7 +931,7 @@ function finishClassComponent(
931 Component: any,
932 shouldUpdate: boolean,
933 hasContext: boolean,
996 - renderExpirationTime: ExpirationTimeOpaque,
934 + renderLanes: Lanes,
935 ) {
936 // Refs should update even if shouldComponentUpdate returns false
937 markRef(current, workInProgress);
@@ -1006,11 +944,7 @@ function finishClassComponent(
944 invalidateContextProvider(workInProgress, Component, false);
945 }
946
1009 - return bailoutOnAlreadyFinishedWork(
1010 - current,
1011 - workInProgress,
1012 - renderExpirationTime,
1013 - );
947 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
948 }
949
950 const instance = workInProgress.stateNode;
@@ -1064,15 +998,10 @@ function finishClassComponent(
998 current,
999 workInProgress,
1000 nextChildren,
1067 - renderExpirationTime,
1001 + renderLanes,
1002 );
1003 } else {
1070 - reconcileChildren(
1071 - current,
1072 - workInProgress,
1073 - nextChildren,
1074 - renderExpirationTime,
1075 - );
1004 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1005 }
1006
1007 // Memoize state using the values we just used to render.
@@ -1102,7 +1031,7 @@ function pushHostRootContext(workInProgress) {
1031 pushHostContainer(workInProgress, root.containerInfo);
1032 }
1033
1105 -function updateHostRoot(current, workInProgress, renderExpirationTime) {
1034 +function updateHostRoot(current, workInProgress, renderLanes) {
1035 pushHostRootContext(workInProgress);
1036 const updateQueue = workInProgress.updateQueue;
1037 invariant(
@@ -1115,20 +1044,14 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
1044 const prevState = workInProgress.memoizedState;
1045 const prevChildren = prevState !== null ? prevState.element : null;
1046 cloneUpdateQueue(current, workInProgress);
1118 - processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
1047 + processUpdateQueue(workInProgress, nextProps, null, renderLanes);
1048 const nextState = workInProgress.memoizedState;
1049 // Caution: React DevTools currently depends on this property
1050 // being called "element".
1051 const nextChildren = nextState.element;
1052 if (nextChildren === prevChildren) {
1124 - // If the state is the same as before, that's a bailout because we had
1125 - // no work that expires at this time.
1053 resetHydrationState();
1127 - return bailoutOnAlreadyFinishedWork(
1128 - current,
1129 - workInProgress,
1130 - renderExpirationTime,
1131 - );
1054 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1055 }
1056 const root: FiberRoot = workInProgress.stateNode;
1057 if (root.hydrate && enterHydrationState(workInProgress)) {
@@ -1141,7 +1064,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
1064 workInProgress,
1065 null,
1066 nextChildren,
1144 - renderExpirationTime,
1067 + renderLanes,
1068 );
1069 workInProgress.child = child;
1070
@@ -1159,12 +1082,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
1082 } else {
1083 // Otherwise reset hydration state in case we aborted and resumed another
1084 // root.
1162 - reconcileChildren(
1163 - current,
1164 - workInProgress,
1165 - nextChildren,
1166 - renderExpirationTime,
1167 - );
1085 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1086 resetHydrationState();
1087 }
1088 return workInProgress.child;
@@ -1173,7 +1091,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
1091 function updateHostComponent(
1092 current: Fiber | null,
1093 workInProgress: Fiber,
1176 - renderExpirationTime: ExpirationTimeOpaque,
1094 + renderLanes: Lanes,
1095 ) {
1096 pushHostContext(workInProgress);
1097
@@ -1221,12 +1139,7 @@ function updateHostComponent(
1139 nextChildren = wrappedChildren;
1140 }
1141
1224 - reconcileChildren(
1225 - current,
1226 - workInProgress,
1227 - nextChildren,
1228 - renderExpirationTime,
1229 - );
1142 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1143 return workInProgress.child;
1144 }
1145
@@ -1243,8 +1156,8 @@ function mountLazyComponent(
1156 _current,
1157 workInProgress,
1158 elementType,
1246 - updateExpirationTime,
1247 - renderExpirationTime,
1159 + updateLanes,
1160 + renderLanes,
1161 ) {
1162 if (_current !== null) {
1163 // A lazy component only mounts if it suspended inside a non-
@@ -1280,7 +1193,7 @@ function mountLazyComponent(
1193 workInProgress,
1194 Component,
1195 resolvedProps,
1283 - renderExpirationTime,
1196 + renderLanes,
1197 );
1198 return child;
1199 }
@@ -1295,7 +1208,7 @@ function mountLazyComponent(
1208 workInProgress,
1209 Component,
1210 resolvedProps,
1298 - renderExpirationTime,
1211 + renderLanes,
1212 );
1213 return child;
1214 }
@@ -1310,7 +1223,7 @@ function mountLazyComponent(
1223 workInProgress,
1224 Component,
1225 resolvedProps,
1313 - renderExpirationTime,
1226 + renderLanes,
1227 );
1228 return child;
1229 }
@@ -1333,8 +1246,8 @@ function mountLazyComponent(
1246 workInProgress,
1247 Component,
1248 resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
1336 - updateExpirationTime,
1337 - renderExpirationTime,
1249 + updateLanes,
1250 + renderLanes,
1251 );
1252 return child;
1253 }
@@ -1346,7 +1259,7 @@ function mountLazyComponent(
1259 workInProgress,
1260 Component,
1261 props,
1349 - renderExpirationTime,
1262 + renderLanes,
1263 );
1264 return child;
1265 }
@@ -1380,7 +1293,7 @@ function mountIncompleteClassComponent(
1293 workInProgress,
1294 Component,
1295 nextProps,
1383 - renderExpirationTime,
1296 + renderLanes,
1297 ) {
1298 if (_current !== null) {
1299 // An incomplete component only mounts if it suspended inside a non-
@@ -1408,15 +1321,10 @@ function mountIncompleteClassComponent(
1321 } else {
1322 hasContext = false;
1323 }
1411 - prepareToReadContext(workInProgress, renderExpirationTime);
1324 + prepareToReadContext(workInProgress, renderLanes);
1325
1326 constructClassInstance(workInProgress, Component, nextProps);
1414 - mountClassInstance(
1415 - workInProgress,
1416 - Component,
1417 - nextProps,
1418 - renderExpirationTime,
1419 - );
1327 + mountClassInstance(workInProgress, Component, nextProps, renderLanes);
1328
1329 return finishClassComponent(
1330 null,
@@ -1424,7 +1332,7 @@ function mountIncompleteClassComponent(
1332 Component,
1333 true,
1334 hasContext,
1427 - renderExpirationTime,
1335 + renderLanes,
1336 );
1337 }
1338
@@ -1432,7 +1340,7 @@ function mountIndeterminateComponent(
1340 _current,
1341 workInProgress,
1342 Component,
1435 - renderExpirationTime,
1343 + renderLanes,
1344 ) {
1345 if (_current !== null) {
1346 // An indeterminate component only mounts if it suspended inside a non-
@@ -1456,7 +1364,7 @@ function mountIndeterminateComponent(
1364 context = getMaskedContext(workInProgress, unmaskedContext);
1365 }
1366
1459 - prepareToReadContext(workInProgress, renderExpirationTime);
1367 + prepareToReadContext(workInProgress, renderLanes);
1368 let value;
1369
1370 if (__DEV__) {
@@ -1489,7 +1397,7 @@ function mountIndeterminateComponent(
1397 Component,
1398 props,
1399 context,
1492 - renderExpirationTime,
1400 + renderLanes,
1401 );
1402 setIsRendering(false);
1403 } else {
@@ -1499,7 +1407,7 @@ function mountIndeterminateComponent(
1407 Component,
1408 props,
1409 context,
1502 - renderExpirationTime,
1410 + renderLanes,
1411 );
1412 }
1413 // React DevTools reads this flag.
@@ -1591,14 +1499,14 @@ function mountIndeterminateComponent(
1499 }
1500
1501 adoptClassInstance(workInProgress, value);
1594 - mountClassInstance(workInProgress, Component, props, renderExpirationTime);
1502 + mountClassInstance(workInProgress, Component, props, renderLanes);
1503 return finishClassComponent(
1504 null,
1505 workInProgress,
1506 Component,
1507 true,
1508 hasContext,
1601 - renderExpirationTime,
1509 + renderLanes,
1510 );
1511 } else {
1512 // Proceed under the assumption that this is a function component
@@ -1624,14 +1532,14 @@ function mountIndeterminateComponent(
1532 Component,
1533 props,
1534 context,
1627 - renderExpirationTime,
1535 + renderLanes,
1536 );
1537 } finally {
1538 reenableLogs();
1539 }
1540 }
1541 }
1634 - reconcileChildren(null, workInProgress, value, renderExpirationTime);
1542 + reconcileChildren(null, workInProgress, value, renderLanes);
1543 if (__DEV__) {
1544 validateFunctionComponentInDev(workInProgress, Component);
1545 }
@@ -1719,30 +1627,21 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
1627
1628 const SUSPENDED_MARKER: SuspenseState = {
1629 dehydrated: null,
1722 - retryTime: NoWork,
1630 + retryLane: NoLane,
1631 };
1632
1725 -function mountSuspenseOffscreenState(
1726 - renderExpirationTime: ExpirationTimeOpaque,
1727 -): OffscreenState {
1633 +function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
1634 return {
1729 - baseTime: renderExpirationTime,
1635 + baseLanes: renderLanes,
1636 };
1637 }
1638
1639 function updateSuspenseOffscreenState(
1640 prevOffscreenState: OffscreenState,
1735 - renderExpirationTime: ExpirationTimeOpaque,
1641 + renderLanes: Lanes,
1642 ): OffscreenState {
1737 - const prevBaseTime = prevOffscreenState.baseTime;
1643 return {
1739 - // Choose whichever time is inclusive of the other one. This represents
1740 - // the union of all the levels that suspended.
1741 - baseTime:
1742 - !isSameExpirationTime(prevBaseTime, (NoWork: ExpirationTimeOpaque)) &&
1743 - !isSameOrHigherPriority(prevBaseTime, renderExpirationTime)
1744 - ? prevBaseTime
1745 - : renderExpirationTime,
1644 + baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
1645 };
1646 }
1647
@@ -1750,7 +1649,7 @@ function shouldRemainOnFallback(
1649 suspenseContext: SuspenseContext,
1650 current: null | Fiber,
1651 workInProgress: Fiber,
1753 - renderExpirationTime: ExpirationTimeOpaque,
1652 + renderLanes: Lanes,
1653 ) {
1654 // If we're already showing a fallback, there are cases where we need to
1655 // remain on that fallback regardless of whether the content has resolved.
@@ -1773,44 +1672,12 @@ function shouldRemainOnFallback(
1672 );
1673 }
1674
1776 -function getRemainingWorkInPrimaryTree(
1777 - current: Fiber,
1778 - workInProgress: Fiber,
1779 - renderExpirationTime,
1780 -) {
1781 - const currentChildExpirationTime = current.childExpirationTime_opaque;
1782 - if (
1783 - !isSameOrHigherPriority(currentChildExpirationTime, renderExpirationTime)
1784 - ) {
1785 - // The highest priority remaining work is not part of this render. So the
1786 - // remaining work has not changed.
1787 - return currentChildExpirationTime;
1788 - }
1789 -
1790 - if ((workInProgress.mode & BlockingMode) !== NoMode) {
1791 - // The highest priority remaining work is part of this render. Since we only
1792 - // keep track of the highest level, we don't know if there's a lower
1793 - // priority level scheduled. As a compromise, we'll render at the lowest
1794 - // known level in the entire tree, since that will include everything.
1795 - // TODO: If expirationTime were a bitmask where each bit represents a
1796 - // separate task thread, this would be: currentChildBits & ~renderBits
1797 - const root = getWorkInProgressRoot();
1798 - if (root !== null) {
1799 - const lastPendingTime = root.lastPendingTime_opaque;
1800 - if (!isSameOrHigherPriority(lastPendingTime, renderExpirationTime)) {
1801 - return lastPendingTime;
1802 - }
1803 - }
1804 - }
1805 - // In legacy mode, there's no work left.
1806 - return NoWork;
1675 +function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes) {
1676 + // TODO: Should not remove render lanes that were pinged during this render
1677 + return removeLanes(current.childLanes, renderLanes);
1678 }
1679
1809 -function updateSuspenseComponent(
1810 - current,
1811 - workInProgress,
1812 - renderExpirationTime,
1813 -) {
1680 +function updateSuspenseComponent(current, workInProgress, renderLanes) {
1681 const nextProps = workInProgress.pendingProps;
1682
1683 // This is used by DevTools to force a boundary to suspend.
@@ -1831,7 +1698,7 @@ function updateSuspenseComponent(
1698 suspenseContext,
1699 current,
1700 workInProgress,
1834 - renderExpirationTime,
1701 + renderLanes,
1702 )
1703 ) {
1704 // Something in this boundary's subtree already suspended. Switch to
@@ -1903,7 +1770,7 @@ function updateSuspenseComponent(
1770 return mountDehydratedSuspenseComponent(
1771 workInProgress,
1772 dehydrated,
1906 - renderExpirationTime,
1773 + renderLanes,
1774 );
1775 }
1776 }
@@ -1917,11 +1784,11 @@ function updateSuspenseComponent(
1784 workInProgress,
1785 nextPrimaryChildren,
1786 nextFallbackChildren,
1920 - renderExpirationTime,
1787 + renderLanes,
1788 );
1789 const primaryChildFragment: Fiber = (workInProgress.child: any);
1790 primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
1924 - renderExpirationTime,
1791 + renderLanes,
1792 );
1793 workInProgress.memoizedState = SUSPENDED_MARKER;
1794 return fallbackFragment;
@@ -1930,7 +1797,7 @@ function updateSuspenseComponent(
1797 return mountSuspensePrimaryChildren(
1798 workInProgress,
1799 nextPrimaryChildren,
1933 - renderExpirationTime,
1800 + renderLanes,
1801 );
1802 }
1803 } else {
@@ -1952,7 +1819,7 @@ function updateSuspenseComponent(
1819 workInProgress,
1820 dehydrated,
1821 prevState,
1955 - renderExpirationTime,
1822 + renderLanes,
1823 );
1824 } else if (
1825 (workInProgress.memoizedState: null | SuspenseState) !== null
@@ -1974,11 +1841,11 @@ function updateSuspenseComponent(
1841 workInProgress,
1842 nextPrimaryChildren,
1843 nextFallbackChildren,
1977 - renderExpirationTime,
1844 + renderLanes,
1845 );
1846 const primaryChildFragment: Fiber = (workInProgress.child: any);
1847 primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
1981 - renderExpirationTime,
1848 + renderLanes,
1849 );
1850 workInProgress.memoizedState = SUSPENDED_MARKER;
1851 return fallbackChildFragment;
@@ -1994,22 +1861,18 @@ function updateSuspenseComponent(
1861 workInProgress,
1862 nextPrimaryChildren,
1863 nextFallbackChildren,
1997 - renderExpirationTime,
1864 + renderLanes,
1865 );
1866 const primaryChildFragment: Fiber = (workInProgress.child: any);
1867 const prevOffscreenState: OffscreenState | null = (current.child: any)
1868 .memoizedState;
1869 primaryChildFragment.memoizedState =
1870 prevOffscreenState === null
2004 - ? mountSuspenseOffscreenState(renderExpirationTime)
2005 - : updateSuspenseOffscreenState(
2006 - prevOffscreenState,
2007 - renderExpirationTime,
2008 - );
2009 - primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
1871 + ? mountSuspenseOffscreenState(renderLanes)
1872 + : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
1873 + primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
1874 current,
2011 - workInProgress,
2012 - renderExpirationTime,
1875 + renderLanes,
1876 );
1877 workInProgress.memoizedState = SUSPENDED_MARKER;
1878 return fallbackChildFragment;
@@ -2019,7 +1882,7 @@ function updateSuspenseComponent(
1882 current,
1883 workInProgress,
1884 nextPrimaryChildren,
2022 - renderExpirationTime,
1885 + renderLanes,
1886 );
1887 workInProgress.memoizedState = null;
1888 return primaryChildFragment;
@@ -2035,22 +1898,18 @@ function updateSuspenseComponent(
1898 workInProgress,
1899 nextPrimaryChildren,
1900 nextFallbackChildren,
2038 - renderExpirationTime,
1901 + renderLanes,
1902 );
1903 const primaryChildFragment: Fiber = (workInProgress.child: any);
1904 const prevOffscreenState: OffscreenState | null = (current.child: any)
1905 .memoizedState;
1906 primaryChildFragment.memoizedState =
1907 prevOffscreenState === null
2045 - ? mountSuspenseOffscreenState(renderExpirationTime)
2046 - : updateSuspenseOffscreenState(
2047 - prevOffscreenState,
2048 - renderExpirationTime,
2049 - );
2050 - primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
1908 + ? mountSuspenseOffscreenState(renderLanes)
1909 + : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
1910 + primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
1911 current,
2052 - workInProgress,
2053 - renderExpirationTime,
1912 + renderLanes,
1913 );
1914 // Skip the primary children, and continue working on the
1915 // fallback children.
@@ -2064,7 +1923,7 @@ function updateSuspenseComponent(
1923 current,
1924 workInProgress,
1925 nextPrimaryChildren,
2067 - renderExpirationTime,
1926 + renderLanes,
1927 );
1928 workInProgress.memoizedState = null;
1929 return primaryChildFragment;
@@ -2076,7 +1935,7 @@ function updateSuspenseComponent(
1935 function mountSuspensePrimaryChildren(
1936 workInProgress,
1937 primaryChildren,
2079 - renderExpirationTime,
1938 + renderLanes,
1939 ) {
1940 const mode = workInProgress.mode;
1941 const primaryChildProps: OffscreenProps = {
@@ -2086,7 +1945,7 @@ function mountSuspensePrimaryChildren(
1945 const primaryChildFragment = createFiberFromOffscreen(
1946 primaryChildProps,
1947 mode,
2089 - renderExpirationTime,
1948 + renderLanes,
1949 null,
1950 );
1951 primaryChildFragment.return = workInProgress;
@@ -2098,7 +1957,7 @@ function mountSuspenseFallbackChildren(
1957 workInProgress,
1958 primaryChildren,
1959 fallbackChildren,
2101 - renderExpirationTime,
1960 + renderLanes,
1961 ) {
1962 const mode = workInProgress.mode;
1963 const progressedPrimaryFragment: Fiber | null = workInProgress.child;
@@ -2114,7 +1973,7 @@ function mountSuspenseFallbackChildren(
1973 // In legacy mode, we commit the primary tree as if it successfully
1974 // completed, even though it's in an inconsistent state.
1975 primaryChildFragment = progressedPrimaryFragment;
2117 - primaryChildFragment.childExpirationTime_opaque = NoWork;
1976 + primaryChildFragment.childLanes = NoLanes;
1977 primaryChildFragment.pendingProps = primaryChildProps;
1978
1979 if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -2131,20 +1990,20 @@ function mountSuspenseFallbackChildren(
1990 fallbackChildFragment = createFiberFromFragment(
1991 fallbackChildren,
1992 mode,
2134 - renderExpirationTime,
1993 + renderLanes,
1994 null,
1995 );
1996 } else {
1997 primaryChildFragment = createFiberFromOffscreen(
1998 primaryChildProps,
1999 mode,
2141 - NoWork,
2000 + NoLanes,
2001 null,
2002 );
2003 fallbackChildFragment = createFiberFromFragment(
2004 fallbackChildren,
2005 mode,
2147 - renderExpirationTime,
2006 + renderLanes,
2007 null,
2008 );
2009 }
@@ -2169,7 +2028,7 @@ function updateSuspensePrimaryChildren(
2028 current,
2029 workInProgress,
2030 primaryChildren,
2172 - renderExpirationTime,
2031 + renderLanes,
2032 ) {
2033 const currentPrimaryChildFragment: Fiber = (current.child: any);
2034 const currentFallbackChildFragment: Fiber | null =
@@ -2183,7 +2042,7 @@ function updateSuspensePrimaryChildren(
2042 },
2043 );
2044 if ((workInProgress.mode & BlockingMode) === NoMode) {
2186 - primaryChildFragment.expirationTime_opaque = renderExpirationTime;
2045 + primaryChildFragment.lanes = renderLanes;
2046 }
2047 primaryChildFragment.return = workInProgress;
2048 primaryChildFragment.sibling = null;
@@ -2203,7 +2062,7 @@ function updateSuspenseFallbackChildren(
2062 workInProgress,
2063 primaryChildren,
2064 fallbackChildren,
2206 - renderExpirationTime,
2065 + renderLanes,
2066 ) {
2067 const mode = workInProgress.mode;
2068 const currentPrimaryChildFragment: Fiber = (current.child: any);
@@ -2221,7 +2080,7 @@ function updateSuspenseFallbackChildren(
2080 // completed, even though it's in an inconsistent state.
2081 const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
2082 primaryChildFragment = progressedPrimaryFragment;
2224 - primaryChildFragment.childExpirationTime_opaque = NoWork;
2083 + primaryChildFragment.childLanes = NoLanes;
2084 primaryChildFragment.pendingProps = primaryChildProps;
2085
2086 if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -2268,7 +2127,7 @@ function updateSuspenseFallbackChildren(
2127 fallbackChildFragment = createFiberFromFragment(
2128 fallbackChildren,
2129 mode,
2271 - renderExpirationTime,
2130 + renderLanes,
2131 null,
2132 );
2133 // Needs a placement effect because the parent (the Suspense boundary) already
@@ -2287,15 +2146,10 @@ function updateSuspenseFallbackChildren(
2146 function retrySuspenseComponentWithoutHydrating(
2147 current: Fiber,
2148 workInProgress: Fiber,
2290 - renderExpirationTime: ExpirationTimeOpaque,
2149 + renderLanes: Lanes,
2150 ) {
2151 // This will add the old fiber to the deletion list
2293 - reconcileChildFibers(
2294 - workInProgress,
2295 - current.child,
2296 - null,
2297 - renderExpirationTime,
2298 - );
2152 + reconcileChildFibers(workInProgress, current.child, null, renderLanes);
2153
2154 // We're now not suspended nor dehydrated.
2155 const nextProps = workInProgress.pendingProps;
@@ -2303,7 +2157,7 @@ function retrySuspenseComponentWithoutHydrating(
2157 const primaryChildFragment = mountSuspensePrimaryChildren(
2158 workInProgress,
2159 primaryChildren,
2306 - renderExpirationTime,
2160 + renderLanes,
2161 );
2162 // Needs a placement effect because the parent (the Suspense boundary) already
2163 // mounted but this is a new fiber.
@@ -2318,19 +2172,19 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
2172 workInProgress,
2173 primaryChildren,
2174 fallbackChildren,
2321 - renderExpirationTime,
2175 + renderLanes,
2176 ) {
2177 const mode = workInProgress.mode;
2178 const primaryChildFragment = createFiberFromOffscreen(
2179 primaryChildren,
2180 mode,
2327 - NoWork,
2181 + NoLanes,
2182 null,
2183 );
2184 const fallbackChildFragment = createFiberFromFragment(
2185 fallbackChildren,
2186 mode,
2333 - renderExpirationTime,
2187 + renderLanes,
2188 null,
2189 );
2190 // Needs a placement effect because the parent (the Suspense
@@ -2345,12 +2199,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
2199 if ((workInProgress.mode & BlockingMode) !== NoMode) {
2200 // We will have dropped the effect list which contains the
2201 // deletion. We need to reconcile to delete the current child.
2348 - reconcileChildFibers(
2349 - workInProgress,
2350 - current.child,
2351 - null,
2352 - renderExpirationTime,
2353 - );
2202 + reconcileChildFibers(workInProgress, current.child, null, renderLanes);
2203 }
2204
2205 return fallbackChildFragment;
@@ -2359,7 +2208,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
2208 function mountDehydratedSuspenseComponent(
2209 workInProgress: Fiber,
2210 suspenseInstance: SuspenseInstance,
2362 - renderExpirationTime: ExpirationTimeOpaque,
2211 + renderLanes: Lanes,
2212 ): null | Fiber {
2213 // During the first pass, we'll bail out and not drill into the children.
2214 // Instead, we'll leave the content in place and try to hydrate it later.
@@ -2373,7 +2222,7 @@ function mountDehydratedSuspenseComponent(
2222 'the server rendered components.',
2223 );
2224 }
2376 - workInProgress.expirationTime_opaque = Sync;
2225 + workInProgress.lanes = laneToLanes(SyncLane);
2226 } else if (isSuspenseInstanceFallback(suspenseInstance)) {
2227 // This is a client-only boundary. Since we won't get any content from the server
2228 // for this, we need to schedule that at a higher priority based on when it would
@@ -2387,17 +2236,16 @@ function mountDehydratedSuspenseComponent(
2236 // time. This will mean that Suspense timeouts are slightly shifted to later than
2237 // they should be.
2238 // Schedule a normal pri update to render this content.
2390 - const newExpirationTime = DefaultUpdateTime;
2239 if (enableSchedulerTracing) {
2392 - markSpawnedWork(newExpirationTime);
2240 + markSpawnedWork(DefaultHydrationLane);
2241 }
2394 - workInProgress.expirationTime_opaque = newExpirationTime;
2242 + workInProgress.lanes = laneToLanes(DefaultHydrationLane);
2243 } else {
2244 // We'll continue hydrating the rest at offscreen priority since we'll already
2245 // be showing the right content coming from the server, it is no rush.
2398 - workInProgress.expirationTime_opaque = Never;
2246 + workInProgress.lanes = laneToLanes(OffscreenLane);
2247 if (enableSchedulerTracing) {
2400 - markSpawnedWork((Never: ExpirationTimeOpaque));
2248 + markSpawnedWork(OffscreenLane);
2249 }
2250 }
2251 return null;
@@ -2408,7 +2256,7 @@ function updateDehydratedSuspenseComponent(
2256 workInProgress: Fiber,
2257 suspenseInstance: SuspenseInstance,
2258 suspenseState: SuspenseState,
2411 - renderExpirationTime: ExpirationTimeOpaque,
2259 + renderLanes: Lanes,
2260 ): null | Fiber {
2261 // We should never be hydrating at this point because it is the first pass,
2262 // but after we've already committed once.
@@ -2418,7 +2266,7 @@ function updateDehydratedSuspenseComponent(
2266 return retrySuspenseComponentWithoutHydrating(
2267 current,
2268 workInProgress,
2421 - renderExpirationTime,
2269 + renderLanes,
2270 );
2271 }
2272
@@ -2429,36 +2277,30 @@ function updateDehydratedSuspenseComponent(
2277 return retrySuspenseComponentWithoutHydrating(
2278 current,
2279 workInProgress,
2432 - renderExpirationTime,
2280 + renderLanes,
2281 );
2282 }
2435 - // We use childExpirationTime to indicate that a child might depend on context, so if
2283 + // We use lanes to indicate that a child might depend on context, so if
2284 // any context has changed, we need to treat is as if the input might have changed.
2437 - const hasContextChanged = isSameOrHigherPriority(
2438 - current.childExpirationTime_opaque,
2439 - renderExpirationTime,
2440 - );
2285 + const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
2286 if (didReceiveUpdate || hasContextChanged) {
2287 // This boundary has changed since the first render. This means that we are now unable to
2443 - // hydrate it. We might still be able to hydrate it using an earlier expiration time, if
2444 - // we are rendering at lower expiration than sync.
2445 - if (
2446 - !isSameOrHigherPriority(
2447 - renderExpirationTime,
2448 - (Sync: ExpirationTimeOpaque),
2449 - )
2450 - ) {
2288 + // hydrate it. We might still be able to hydrate it using a higher priority lane.
2289 + const root = getWorkInProgressRoot();
2290 + if (root !== null) {
2291 + const attemptHydrationAtLane = getBumpedLaneForHydration(
2292 + root,
2293 + renderLanes,
2294 + );
2295 if (
2452 - isSameOrHigherPriority(renderExpirationTime, suspenseState.retryTime)
2296 + attemptHydrationAtLane !== NoLane &&
2297 + attemptHydrationAtLane !== suspenseState.retryLane
2298 ) {
2454 - // This render is even higher pri than we've seen before, let's try again
2455 - // at even higher pri.
2456 - const attemptHydrationAtExpirationTime = bumpPriorityHigher(
2457 - renderExpirationTime,
2458 - );
2459 - suspenseState.retryTime = attemptHydrationAtExpirationTime;
2460 - scheduleUpdateOnFiber(current, attemptHydrationAtExpirationTime);
2461 - // TODO: Early abort this render.
2299 + // Intentionally mutating since this render will get interrupted. This
2300 + // is one of the very rare times where we mutate the current tree
2301 + // during the render phase.
2302 + suspenseState.retryLane = attemptHydrationAtLane;
2303 + scheduleUpdateOnFiber(current, attemptHydrationAtLane);
2304 } else {
2305 // We have already tried to ping at a higher priority than we're rendering with
2306 // so if we got here, we must have failed to hydrate at those levels. We must
@@ -2468,6 +2310,7 @@ function updateDehydratedSuspenseComponent(
2310 // an opportunity to hydrate before this pass commits.
2311 }
2312 }
2313 +
2314 // If we have scheduled higher pri work above, this will probably just abort the render
2315 // since we now have higher priority work, but in case it doesn't, we need to prepare to
2316 // render something, if we time out. Even if that requires us to delete everything and
@@ -2477,7 +2320,7 @@ function updateDehydratedSuspenseComponent(
2320 return retrySuspenseComponentWithoutHydrating(
2321 current,
2322 workInProgress,
2480 - renderExpirationTime,
2323 + renderLanes,
2324 );
2325 } else if (isSuspenseInstancePending(suspenseInstance)) {
2326 // This component is still pending more data from the server, so we can't hydrate its
@@ -2509,7 +2352,7 @@ function updateDehydratedSuspenseComponent(
2352 const primaryChildFragment = mountSuspensePrimaryChildren(
2353 workInProgress,
2354 primaryChildren,
2512 - renderExpirationTime,
2355 + renderLanes,
2356 );
2357 // Mark the children as hydrating. This is a fast path to know whether this
2358 // tree is part of a hydrating tree. This is used to determine if a child
@@ -2522,32 +2365,19 @@ function updateDehydratedSuspenseComponent(
2365 }
2366 }
2367
2525 -function scheduleWorkOnFiber(
2526 - fiber: Fiber,
2527 - renderExpirationTime: ExpirationTimeOpaque,
2528 -) {
2529 - if (
2530 - !isSameOrHigherPriority(fiber.expirationTime_opaque, renderExpirationTime)
2531 - ) {
2532 - fiber.expirationTime_opaque = renderExpirationTime;
2533 - }
2368 +function scheduleWorkOnFiber(fiber: Fiber, renderLanes: Lanes) {
2369 + fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
2370 const alternate = fiber.alternate;
2535 - if (
2536 - alternate !== null &&
2537 - !isSameOrHigherPriority(
2538 - alternate.expirationTime_opaque,
2539 - renderExpirationTime,
2540 - )
2541 - ) {
2542 - alternate.expirationTime_opaque = renderExpirationTime;
2371 + if (alternate !== null) {
2372 + alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
2373 }
2544 - scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
2374 + scheduleWorkOnParentPath(fiber.return, renderLanes);
2375 }
2376
2377 function propagateSuspenseContextChange(
2378 workInProgress: Fiber,
2379 firstChild: null | Fiber,
2550 - renderExpirationTime: ExpirationTimeOpaque,
2380 + renderLanes: Lanes,
2381 ): void {
2382 // Mark any Suspense boundaries with fallbacks as having work to do.
2383 // If they were previously forced into fallbacks, they may now be able
@@ -2557,7 +2387,7 @@ function propagateSuspenseContextChange(
2387 if (node.tag === SuspenseComponent) {
2388 const state: SuspenseState | null = node.memoizedState;
2389 if (state !== null) {
2560 - scheduleWorkOnFiber(node, renderExpirationTime);
2390 + scheduleWorkOnFiber(node, renderLanes);
2391 }
2392 } else if (node.tag === SuspenseListComponent) {
2393 // If the tail is hidden there might not be an Suspense boundaries
@@ -2565,7 +2395,7 @@ function propagateSuspenseContextChange(
2395 // list itself.
2396 // We don't have to traverse to the children of the list since
2397 // the list will propagate the change when it rerenders.
2568 - scheduleWorkOnFiber(node, renderExpirationTime);
2398 + scheduleWorkOnFiber(node, renderLanes);
2399 } else if (node.child !== null) {
2400 node.child.return = node;
2401 node = node.child;
@@ -2797,7 +2627,7 @@ function initSuspenseListRenderState(
2627 function updateSuspenseListComponent(
2628 current: Fiber | null,
2629 workInProgress: Fiber,
2800 - renderExpirationTime: ExpirationTimeOpaque,
2630 + renderLanes: Lanes,
2631 ) {
2632 const nextProps = workInProgress.pendingProps;
2633 const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
@@ -2808,7 +2638,7 @@ function updateSuspenseListComponent(
2638 validateTailOptions(tailMode, revealOrder);
2639 validateSuspenseListChildren(newChildren, revealOrder);
2640
2811 - reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2641 + reconcileChildren(current, workInProgress, newChildren, renderLanes);
2642
2643 let suspenseContext: SuspenseContext = suspenseStackCursor.current;
2644
@@ -2832,7 +2662,7 @@ function updateSuspenseListComponent(
2662 propagateSuspenseContextChange(
2663 workInProgress,
2664 workInProgress.child,
2835 - renderExpirationTime,
2665 + renderLanes,
2666 );
2667 }
2668 suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
@@ -2925,7 +2755,7 @@ function updateSuspenseListComponent(
2755 function updatePortalComponent(
2756 current: Fiber | null,
2757 workInProgress: Fiber,
2928 - renderExpirationTime: ExpirationTimeOpaque,
2758 + renderLanes: Lanes,
2759 ) {
2760 pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
2761 const nextChildren = workInProgress.pendingProps;
@@ -2939,15 +2769,10 @@ function updatePortalComponent(
2769 workInProgress,
2770 null,
2771 nextChildren,
2942 - renderExpirationTime,
2772 + renderLanes,
2773 );
2774 } else {
2945 - reconcileChildren(
2946 - current,
2947 - workInProgress,
2948 - nextChildren,
2949 - renderExpirationTime,
2950 - );
2775 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
2776 }
2777 return workInProgress.child;
2778 }
@@ -2955,7 +2780,7 @@ function updatePortalComponent(
2780 function updateContextProvider(
2781 current: Fiber | null,
2782 workInProgress: Fiber,
2958 - renderExpirationTime: ExpirationTimeOpaque,
2783 + renderLanes: Lanes,
2784 ) {
2785 const providerType: ReactProviderType<any> = workInProgress.type;
2786 const context: ReactContext<any> = providerType._context;
@@ -2987,23 +2812,18 @@ function updateContextProvider(
2812 return bailoutOnAlreadyFinishedWork(
2813 current,
2814 workInProgress,
2990 - renderExpirationTime,
2815 + renderLanes,
2816 );
2817 }
2818 } else {
2819 // The context value changed. Search for matching consumers and schedule
2820 // them to update.
2996 - propagateContextChange(
2997 - workInProgress,
2998 - context,
2999 - changedBits,
3000 - renderExpirationTime,
3001 - );
2821 + propagateContextChange(workInProgress, context, changedBits, renderLanes);
2822 }
2823 }
2824
2825 const newChildren = newProps.children;
3006 - reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2826 + reconcileChildren(current, workInProgress, newChildren, renderLanes);
2827 return workInProgress.child;
2828 }
2829
@@ -3012,7 +2832,7 @@ let hasWarnedAboutUsingContextAsConsumer = false;
2832 function updateContextConsumer(
2833 current: Fiber | null,
2834 workInProgress: Fiber,
3015 - renderExpirationTime: ExpirationTimeOpaque,
2835 + renderLanes: Lanes,
2836 ) {
2837 let context: ReactContext<any> = workInProgress.type;
2838 // The logic below for Context differs depending on PROD or DEV mode. In
@@ -3054,7 +2874,7 @@ function updateContextConsumer(
2874 }
2875 }
2876
3057 - prepareToReadContext(workInProgress, renderExpirationTime);
2877 + prepareToReadContext(workInProgress, renderLanes);
2878 const newValue = readContext(context, newProps.unstable_observedBits);
2879 let newChildren;
2880 if (__DEV__) {
@@ -3068,15 +2888,11 @@ function updateContextConsumer(
2888
2889 // React DevTools reads this flag.
2890 workInProgress.effectTag |= PerformedWork;
3071 - reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2891 + reconcileChildren(current, workInProgress, newChildren, renderLanes);
2892 return workInProgress.child;
2893 }
2894
3075 -function updateFundamentalComponent(
3076 - current,
3077 - workInProgress,
3078 - renderExpirationTime,
3079 -) {
2895 +function updateFundamentalComponent(current, workInProgress, renderLanes) {
2896 const fundamentalImpl = workInProgress.type.impl;
2897 if (fundamentalImpl.reconcileChildren === false) {
2898 return null;
@@ -3084,25 +2900,15 @@ function updateFundamentalComponent(
2900 const nextProps = workInProgress.pendingProps;
2901 const nextChildren = nextProps.children;
2902
3087 - reconcileChildren(
3088 - current,
3089 - workInProgress,
3090 - nextChildren,
3091 - renderExpirationTime,
3092 - );
2903 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
2904 return workInProgress.child;
2905 }
2906
3096 -function updateScopeComponent(current, workInProgress, renderExpirationTime) {
2907 +function updateScopeComponent(current, workInProgress, renderLanes) {
2908 const nextProps = workInProgress.pendingProps;
2909 const nextChildren = nextProps.children;
2910
3100 - reconcileChildren(
3101 - current,
3102 - workInProgress,
3103 - nextChildren,
3104 - renderExpirationTime,
3105 - );
2911 + reconcileChildren(current, workInProgress, nextChildren, renderLanes);
2912 return workInProgress.child;
2913 }
2914
@@ -3113,7 +2919,7 @@ export function markWorkInProgressReceivedUpdate() {
2919 function bailoutOnAlreadyFinishedWork(
2920 current: Fiber | null,
2921 workInProgress: Fiber,
3116 - renderExpirationTime: ExpirationTimeOpaque,
2922 + renderLanes: Lanes,
2923 ): Fiber | null {
2924 if (current !== null) {
2925 // Reuse previous dependencies
@@ -3125,16 +2931,10 @@ function bailoutOnAlreadyFinishedWork(
2931 stopProfilerTimerIfRunning(workInProgress);
2932 }
2933
3128 - const updateExpirationTime = workInProgress.expirationTime_opaque;
3129 - if (
3130 - !isSameExpirationTime(updateExpirationTime, (NoWork: ExpirationTimeOpaque))
3131 - ) {
3132 - markUnprocessedUpdateTime(updateExpirationTime);
3133 - }
2934 + markSkippedUpdateLanes(workInProgress.lanes);
2935
2936 // Check if the children have any pending work.
3136 - const childExpirationTime = workInProgress.childExpirationTime_opaque;
3137 - if (!isSameOrHigherPriority(childExpirationTime, renderExpirationTime)) {
2937 + if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
2938 // The children don't have any work either. We can skip them.
2939 // TODO: Once we add back resuming, we should check if the children are
2940 // a work-in-progress set. If so, we need to transfer their effects.
@@ -3213,9 +3013,9 @@ function remountFiber(
3013 function beginWork(
3014 current: Fiber | null,
3015 workInProgress: Fiber,
3216 - renderExpirationTime: ExpirationTimeOpaque,
3016 + renderLanes: Lanes,
3017 ): Fiber | null {
3218 - const updateExpirationTime = workInProgress.expirationTime_opaque;
3018 + const updateLanes = workInProgress.lanes;
3019
3020 if (__DEV__) {
3021 if (workInProgress._debugNeedsRemount && current !== null) {
@@ -3229,7 +3029,7 @@ function beginWork(
3029 workInProgress.pendingProps,
3030 workInProgress._debugOwner || null,
3031 workInProgress.mode,
3232 - workInProgress.expirationTime_opaque,
3032 + workInProgress.lanes,
3033 ),
3034 );
3035 }
@@ -3248,9 +3048,7 @@ function beginWork(
3048 // If props or context changed, mark the fiber as having performed work.
3049 // This may be unset if the props are determined to be equal later (memo).
3050 didReceiveUpdate = true;
3251 - } else if (
3252 - !isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)
3253 - ) {
3051 + } else if (!includesSomeLane(renderLanes, updateLanes)) {
3052 didReceiveUpdate = false;
3053 // This fiber does not have any pending work. Bailout without entering
3054 // the begin phase. There's still some bookkeeping we that needs to be done
@@ -3284,9 +3082,9 @@ function beginWork(
3082 case Profiler:
3083 if (enableProfilerTimer) {
3084 // Profiler should only call onRender when one of its descendants actually rendered.
3287 - const hasChildWork = isSameOrHigherPriority(
3288 - workInProgress.childExpirationTime_opaque,
3289 - renderExpirationTime,
3085 + const hasChildWork = includesSomeLane(
3086 + renderLanes,
3087 + workInProgress.childLanes,
3088 );
3089 if (hasChildWork) {
3090 workInProgress.effectTag |= Update;
@@ -3321,20 +3119,14 @@ function beginWork(
3119 // go straight to the fallback. Check the priority of the primary
3120 // child fragment.
3121 const primaryChildFragment: Fiber = (workInProgress.child: any);
3324 - const primaryChildExpirationTime =
3325 - primaryChildFragment.childExpirationTime_opaque;
3326 - if (
3327 - isSameOrHigherPriority(
3328 - primaryChildExpirationTime,
3329 - renderExpirationTime,
3330 - )
3331 - ) {
3122 + const primaryChildLanes = primaryChildFragment.childLanes;
3123 + if (includesSomeLane(renderLanes, primaryChildLanes)) {
3124 // The primary children have pending work. Use the normal path
3125 // to attempt to render the primary children again.
3126 return updateSuspenseComponent(
3127 current,
3128 workInProgress,
3337 - renderExpirationTime,
3129 + renderLanes,
3130 );
3131 } else {
3132 // The primary child fragment does not have pending work marked
@@ -3348,7 +3140,7 @@ function beginWork(
3140 const child = bailoutOnAlreadyFinishedWork(
3141 current,
3142 workInProgress,
3351 - renderExpirationTime,
3143 + renderLanes,
3144 );
3145 if (child !== null) {
3146 // The fallback children have pending work. Skip over the
@@ -3370,9 +3162,9 @@ function beginWork(
3162 const didSuspendBefore =
3163 (current.effectTag & DidCapture) !== NoEffect;
3164
3373 - const hasChildWork = isSameOrHigherPriority(
3374 - workInProgress.childExpirationTime_opaque,
3375 - renderExpirationTime,
3165 + const hasChildWork = includesSomeLane(
3166 + renderLanes,
3167 + workInProgress.childLanes,
3168 );
3169
3170 if (didSuspendBefore) {
@@ -3385,7 +3177,7 @@ function beginWork(
3177 return updateSuspenseListComponent(
3178 current,
3179 workInProgress,
3388 - renderExpirationTime,
3180 + renderLanes,
3181 );
3182 }
3183 // If none of the children had any work, that means that none of
@@ -3426,19 +3218,11 @@ function beginWork(
3218 // TODO: Probably should refactor `beginWork` to split the bailout
3219 // path from the normal path. I'm tempted to do a labeled break here
3220 // but I won't :)
3429 - workInProgress.expirationTime_opaque = NoWork;
3430 - return updateOffscreenComponent(
3431 - current,
3432 - workInProgress,
3433 - renderExpirationTime,
3434 - );
3221 + workInProgress.lanes = NoLanes;
3222 + return updateOffscreenComponent(current, workInProgress, renderLanes);
3223 }
3224 }
3437 - return bailoutOnAlreadyFinishedWork(
3438 - current,
3439 - workInProgress,
3440 - renderExpirationTime,
3441 - );
3225 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3226 } else {
3227 // An update was scheduled on this fiber, but there are no new props
3228 // nor legacy context. Set this to false. If an update queue or context
@@ -3455,7 +3239,7 @@ function beginWork(
3239 // the update queue. However, there's an exception: SimpleMemoComponent
3240 // sometimes bails out later in the begin phase. This indicates that we should
3241 // move this assignment out of the common path and into each branch.
3458 - workInProgress.expirationTime_opaque = NoWork;
3242 + workInProgress.lanes = NoLanes;
3243
3244 switch (workInProgress.tag) {
3245 case IndeterminateComponent: {
@@ -3463,7 +3247,7 @@ function beginWork(
3247 current,
3248 workInProgress,
3249 workInProgress.type,
3466 - renderExpirationTime,
3250 + renderLanes,
3251 );
3252 }
3253 case LazyComponent: {
@@ -3472,8 +3256,8 @@ function beginWork(
3256 current,
3257 workInProgress,
3258 elementType,
3475 - updateExpirationTime,
3476 - renderExpirationTime,
3259 + updateLanes,
3260 + renderLanes,
3261 );
3262 }
3263 case FunctionComponent: {
@@ -3488,7 +3272,7 @@ function beginWork(
3272 workInProgress,
3273 Component,
3274 resolvedProps,
3491 - renderExpirationTime,
3275 + renderLanes,
3276 );
3277 }
3278 case ClassComponent: {
@@ -3503,27 +3287,19 @@ function beginWork(
3287 workInProgress,
3288 Component,
3289 resolvedProps,
3506 - renderExpirationTime,
3290 + renderLanes,
3291 );
3292 }
3293 case HostRoot:
3510 - return updateHostRoot(current, workInProgress, renderExpirationTime);
3294 + return updateHostRoot(current, workInProgress, renderLanes);
3295 case HostComponent:
3512 - return updateHostComponent(current, workInProgress, renderExpirationTime);
3296 + return updateHostComponent(current, workInProgress, renderLanes);
3297 case HostText:
3298 return updateHostText(current, workInProgress);
3299 case SuspenseComponent:
3516 - return updateSuspenseComponent(
3517 - current,
3518 - workInProgress,
3519 - renderExpirationTime,
3520 - );
3300 + return updateSuspenseComponent(current, workInProgress, renderLanes);
3301 case HostPortal:
3522 - return updatePortalComponent(
3523 - current,
3524 - workInProgress,
3525 - renderExpirationTime,
3526 - );
3302 + return updatePortalComponent(current, workInProgress, renderLanes);
3303 case ForwardRef: {
3304 const type = workInProgress.type;
3305 const unresolvedProps = workInProgress.pendingProps;
@@ -3536,27 +3312,19 @@ function beginWork(
3312 workInProgress,
3313 type,
3314 resolvedProps,
3539 - renderExpirationTime,
3315 + renderLanes,
3316 );
3317 }
3318 case Fragment:
3543 - return updateFragment(current, workInProgress, renderExpirationTime);
3319 + return updateFragment(current, workInProgress, renderLanes);
3320 case Mode:
3545 - return updateMode(current, workInProgress, renderExpirationTime);
3321 + return updateMode(current, workInProgress, renderLanes);
3322 case Profiler:
3547 - return updateProfiler(current, workInProgress, renderExpirationTime);
3323 + return updateProfiler(current, workInProgress, renderLanes);
3324 case ContextProvider:
3549 - return updateContextProvider(
3550 - current,
3551 - workInProgress,
3552 - renderExpirationTime,
3553 - );
3325 + return updateContextProvider(current, workInProgress, renderLanes);
3326 case ContextConsumer:
3555 - return updateContextConsumer(
3556 - current,
3557 - workInProgress,
3558 - renderExpirationTime,
3559 - );
3327 + return updateContextConsumer(current, workInProgress, renderLanes);
3328 case MemoComponent: {
3329 const type = workInProgress.type;
3330 const unresolvedProps = workInProgress.pendingProps;
@@ -3581,8 +3349,8 @@ function beginWork(
3349 workInProgress,
3350 type,
3351 resolvedProps,
3584 - updateExpirationTime,
3585 - renderExpirationTime,
3352 + updateLanes,
3353 + renderLanes,
3354 );
3355 }
3356 case SimpleMemoComponent: {
@@ -3591,8 +3359,8 @@ function beginWork(
3359 workInProgress,
3360 workInProgress.type,
3361 workInProgress.pendingProps,
3594 - updateExpirationTime,
3595 - renderExpirationTime,
3362 + updateLanes,
3363 + renderLanes,
3364 );
3365 }
3366 case IncompleteClassComponent: {
@@ -3607,33 +3375,21 @@ function beginWork(
3375 workInProgress,
3376 Component,
3377 resolvedProps,
3610 - renderExpirationTime,
3378 + renderLanes,
3379 );
3380 }
3381 case SuspenseListComponent: {
3614 - return updateSuspenseListComponent(
3615 - current,
3616 - workInProgress,
3617 - renderExpirationTime,
3618 - );
3382 + return updateSuspenseListComponent(current, workInProgress, renderLanes);
3383 }
3384 case FundamentalComponent: {
3385 if (enableFundamentalAPI) {
3622 - return updateFundamentalComponent(
3623 - current,
3624 - workInProgress,
3625 - renderExpirationTime,
3626 - );
3386 + return updateFundamentalComponent(current, workInProgress, renderLanes);
3387 }
3388 break;
3389 }
3390 case ScopeComponent: {
3391 if (enableScopeAPI) {
3632 - return updateScopeComponent(
3633 - current,
3634 - workInProgress,
3635 - renderExpirationTime,
3636 - );
3392 + return updateScopeComponent(current, workInProgress, renderLanes);
3393 }
3394 break;
3395 }
@@ -3641,29 +3397,15 @@ function beginWork(
3397 if (enableBlocksAPI) {
3398 const block = workInProgress.type;
3399 const props = workInProgress.pendingProps;
3644 - return updateBlock(
3645 - current,
3646 - workInProgress,
3647 - block,
3648 - props,
3649 - renderExpirationTime,
3650 - );
3400 + return updateBlock(current, workInProgress, block, props, renderLanes);
3401 }
3402 break;
3403 }
3404 case OffscreenComponent: {
3655 - return updateOffscreenComponent(
3656 - current,
3657 - workInProgress,
3658 - renderExpirationTime,
3659 - );
3405 + return updateOffscreenComponent(current, workInProgress, renderLanes);
3406 }
3407 case LegacyHiddenComponent: {
3662 - return updateLegacyHiddenComponent(
3663 - current,
3664 - workInProgress,
3665 - renderExpirationTime,
3666 - );
3408 + return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
3409 }
3410 }
3411 invariant(
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+20 -30
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 +import type {Lanes} from './ReactFiberLane';
12 import type {UpdateQueue} from './ReactUpdateQueue.new';
13
14 import * as React from 'react';
@@ -40,7 +40,7 @@ import {
40 initializeUpdateQueue,
41 cloneUpdateQueue,
42 } from './ReactUpdateQueue.new';
43 -import {NoWork, isSameExpirationTime} from './ReactFiberExpirationTime.new';
43 +import {NoLanes} from './ReactFiberLane';
44 import {
45 cacheContext,
46 getMaskedContext,
@@ -51,7 +51,7 @@ import {
51 import {readContext} from './ReactFiberNewContext.new';
52 import {
53 requestEventTime,
54 - requestUpdateExpirationTime,
54 + requestUpdateLane,
55 scheduleUpdateOnFiber,
56 } from './ReactFiberWorkLoop.new';
57 import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
@@ -177,12 +177,7 @@ export function applyDerivedStateFromProps(
177
178 // Once the update queue is empty, persist the derived state onto the
179 // base state.
180 - if (
181 - isSameExpirationTime(
182 - workInProgress.expirationTime_opaque,
183 - (NoWork: ExpirationTimeOpaque),
184 - )
185 - ) {
180 + if (workInProgress.lanes === NoLanes) {
181 // Queue is always non-null for classes
182 const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
183 updateQueue.baseState = memoizedState;
@@ -195,9 +190,9 @@ const classComponentUpdater = {
190 const fiber = getInstance(inst);
191 const eventTime = requestEventTime();
192 const suspenseConfig = requestCurrentSuspenseConfig();
198 - const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
193 + const lane = requestUpdateLane(fiber, suspenseConfig);
194
200 - const update = createUpdate(eventTime, expirationTime, suspenseConfig);
195 + const update = createUpdate(eventTime, lane, suspenseConfig);
196 update.payload = payload;
197 if (callback !== undefined && callback !== null) {
198 if (__DEV__) {
@@ -207,15 +202,15 @@ const classComponentUpdater = {
202 }
203
204 enqueueUpdate(fiber, update);
210 - scheduleUpdateOnFiber(fiber, expirationTime);
205 + scheduleUpdateOnFiber(fiber, lane);
206 },
207 enqueueReplaceState(inst, payload, callback) {
208 const fiber = getInstance(inst);
209 const eventTime = requestEventTime();
210 const suspenseConfig = requestCurrentSuspenseConfig();
216 - const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
211 + const lane = requestUpdateLane(fiber, suspenseConfig);
212
218 - const update = createUpdate(eventTime, expirationTime, suspenseConfig);
213 + const update = createUpdate(eventTime, lane, suspenseConfig);
214 update.tag = ReplaceState;
215 update.payload = payload;
216
@@ -227,15 +222,15 @@ const classComponentUpdater = {
222 }
223
224 enqueueUpdate(fiber, update);
230 - scheduleUpdateOnFiber(fiber, expirationTime);
225 + scheduleUpdateOnFiber(fiber, lane);
226 },
227 enqueueForceUpdate(inst, callback) {
228 const fiber = getInstance(inst);
229 const eventTime = requestEventTime();
230 const suspenseConfig = requestCurrentSuspenseConfig();
236 - const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
231 + const lane = requestUpdateLane(fiber, suspenseConfig);
232
238 - const update = createUpdate(eventTime, expirationTime, suspenseConfig);
233 + const update = createUpdate(eventTime, lane, suspenseConfig);
234 update.tag = ForceUpdate;
235
236 if (callback !== undefined && callback !== null) {
@@ -246,7 +241,7 @@ const classComponentUpdater = {
241 }
242
243 enqueueUpdate(fiber, update);
249 - scheduleUpdateOnFiber(fiber, expirationTime);
244 + scheduleUpdateOnFiber(fiber, lane);
245 },
246 };
247
@@ -771,7 +766,7 @@ function mountClassInstance(
766 workInProgress: Fiber,
767 ctor: any,
768 newProps: any,
774 - renderExpirationTime: ExpirationTimeOpaque,
769 + renderLanes: Lanes,
770 ): void {
771 if (__DEV__) {
772 checkClassInstance(workInProgress, ctor, newProps);
@@ -823,7 +818,7 @@ function mountClassInstance(
818 }
819 }
820
826 - processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
821 + processUpdateQueue(workInProgress, newProps, instance, renderLanes);
822 instance.state = workInProgress.memoizedState;
823
824 const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
@@ -848,12 +843,7 @@ function mountClassInstance(
843 callComponentWillMount(workInProgress, instance);
844 // If we had additional state updates during this life-cycle, let's
845 // process them now.
851 - processUpdateQueue(
852 - workInProgress,
853 - newProps,
854 - instance,
855 - renderExpirationTime,
856 - );
846 + processUpdateQueue(workInProgress, newProps, instance, renderLanes);
847 instance.state = workInProgress.memoizedState;
848 }
849
@@ -866,7 +856,7 @@ function resumeMountClassInstance(
856 workInProgress: Fiber,
857 ctor: any,
858 newProps: any,
869 - renderExpirationTime: ExpirationTimeOpaque,
859 + renderLanes: Lanes,
860 ): boolean {
861 const instance = workInProgress.stateNode;
862
@@ -917,7 +907,7 @@ function resumeMountClassInstance(
907
908 const oldState = workInProgress.memoizedState;
909 let newState = (instance.state = oldState);
920 - processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
910 + processUpdateQueue(workInProgress, newProps, instance, renderLanes);
911 newState = workInProgress.memoizedState;
912 if (
913 oldProps === newProps &&
@@ -1001,7 +991,7 @@ function updateClassInstance(
991 workInProgress: Fiber,
992 ctor: any,
993 newProps: any,
1004 - renderExpirationTime: ExpirationTimeOpaque,
994 + renderLanes: Lanes,
995 ): boolean {
996 const instance = workInProgress.stateNode;
997
@@ -1058,7 +1048,7 @@ function updateClassInstance(
1048
1049 const oldState = workInProgress.memoizedState;
1050 let newState = (instance.state = oldState);
1061 - processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
1051 + processUpdateQueue(workInProgress, newProps, instance, renderLanes);
1052 newState = workInProgress.memoizedState;
1053
1054 if (
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+2 -2
@@ -17,7 +17,7 @@ import type {
17 } from './ReactFiberHostConfig';
18 import type {Fiber} from './ReactInternalTypes';
19 import type {FiberRoot} from './ReactInternalTypes';
20 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
20 +import type {Lanes} from './ReactFiberLane';
21 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
22 import type {UpdateQueue} from './ReactUpdateQueue.new';
23 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
@@ -516,7 +516,7 @@ function commitLifeCycles(
516 finishedRoot: FiberRoot,
517 current: Fiber | null,
518 finishedWork: Fiber,
519 - committedExpirationTime: ExpirationTimeOpaque,
519 + committedLanes: Lanes,
520 ): void {
521 switch (finishedWork.tag) {
522 case FunctionComponent:
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+11 -14
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 +import type {Lanes} from './ReactFiberLane';
12 import type {
13 ReactFundamentalComponentInstance,
14 ReactScopeInstance,
@@ -133,10 +133,10 @@ import {
133 renderDidSuspend,
134 renderDidSuspendDelayIfPossible,
135 renderHasNotSuspendedYet,
136 - popRenderExpirationTime,
136 + popRenderLanes,
137 } from './ReactFiberWorkLoop.new';
138 import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
139 -import {Never, isSameOrHigherPriority} from './ReactFiberExpirationTime.new';
139 +import {OffscreenLane} from './ReactFiberLane';
140 import {resetChildFibers} from './ReactChildFiber.new';
141 import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new';
142 import {createScopeInstance} from './ReactFiberScope.new';
@@ -644,7 +644,7 @@ function cutOffTailIfNeeded(
644 function completeWork(
645 current: Fiber | null,
646 workInProgress: Fiber,
647 - renderExpirationTime: ExpirationTimeOpaque,
647 + renderLanes: Lanes,
648 ): Fiber | null {
649 const newProps = workInProgress.pendingProps;
650
@@ -857,7 +857,7 @@ function completeWork(
857 );
858 prepareToHydrateHostSuspenseInstance(workInProgress);
859 if (enableSchedulerTracing) {
860 - markSpawnedWork((Never: ExpirationTimeOpaque));
860 + markSpawnedWork(OffscreenLane);
861 }
862 return null;
863 } else {
@@ -882,7 +882,7 @@ function completeWork(
882
883 if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
884 // Something suspended. Re-render with the fallback children.
885 - workInProgress.expirationTime_opaque = renderExpirationTime;
885 + workInProgress.lanes = renderLanes;
886 // Do not reset the effect list.
887 return workInProgress;
888 }
@@ -1051,7 +1051,7 @@ function completeWork(
1051 }
1052 workInProgress.lastEffect = renderState.lastEffect;
1053 // Reset the child fibers to their original state.
1054 - resetChildFibers(workInProgress, renderExpirationTime);
1054 + resetChildFibers(workInProgress, renderLanes);
1055
1056 // Set up the Suspense Context to force suspense and immediately
1057 // rerender the children.
@@ -1111,10 +1111,7 @@ function completeWork(
1111 // the expiration.
1112 now() * 2 - renderState.renderingStartTime >
1113 renderState.tailExpiration &&
1114 - !isSameOrHigherPriority(
1115 - (Never: ExpirationTimeOpaque),
1116 - renderExpirationTime,
1117 - )
1114 + renderLanes !== OffscreenLane
1115 ) {
1116 // We have now passed our CPU deadline and we'll just give up further
1117 // attempts to render the main content and only render fallbacks.
@@ -1129,9 +1126,9 @@ function completeWork(
1126 // them, then they really have the same priority as this render.
1127 // So we'll pick it back up the very next render pass once we've had
1128 // an opportunity to yield for paint.
1132 - workInProgress.expirationTime_opaque = renderExpirationTime;
1129 + workInProgress.lanes = renderLanes;
1130 if (enableSchedulerTracing) {
1134 - markSpawnedWork(renderExpirationTime);
1131 + markSpawnedWork(renderLanes);
1132 }
1133 }
1134 }
@@ -1297,7 +1294,7 @@ function completeWork(
1294 break;
1295 case OffscreenComponent:
1296 case LegacyHiddenComponent: {
1300 - popRenderExpirationTime(workInProgress);
1297 + popRenderLanes(workInProgress);
1298 if (current !== null) {
1299 const nextState: OffscreenState | null = workInProgress.memoizedState;
1300 const prevState: OffscreenState | null = current.memoizedState;
packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js
+2 -2
@@ -19,7 +19,7 @@ import {
19 DEPRECATED_mountResponderInstance,
20 DEPRECATED_unmountResponderInstance,
21 } from './ReactFiberHostConfig';
22 -import {NoWork} from './ReactFiberExpirationTime.new';
22 +import {NoLanes} from './ReactFiberLane';
23
24 import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
25
@@ -154,7 +154,7 @@ export function updateDeprecatedEventListeners(
154 if (listeners != null) {
155 if (dependencies === null) {
156 dependencies = fiber.dependencies_new = {
157 - expirationTime: NoWork,
157 + lanes: NoLanes,
158 firstContext: null,
159 responders: new Map(),
160 };
packages/react-reconciler/src/ReactFiberExpirationTime.new.js deleted
-92
@@ -1,92 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {ReactPriorityLevel} from './ReactInternalTypes';
11 -
12 -import {MAX_SIGNED_31_BIT_INT} from './MaxInts';
13 -
14 -import {
15 - ImmediatePriority,
16 - UserBlockingPriority,
17 - NormalPriority,
18 - IdlePriority,
19 -} from './SchedulerWithReactIntegration.new';
20 -
21 -export opaque type ExpirationTimeOpaque = number;
22 -
23 -export const NoWork: ExpirationTimeOpaque = 0;
24 -// TODO: Think of a better name for Never. The key difference with Idle is that
25 -// Never work can be committed in an inconsistent state without tearing the UI.
26 -// The main example is offscreen content, like a hidden subtree. So one possible
27 -// name is Offscreen. However, it also includes dehydrated Suspense boundaries,
28 -// which are inconsistent in the sense that they haven't finished yet, but
29 -// aren't visibly inconsistent because the server rendered HTML matches what the
30 -// hydrated tree would look like.
31 -export const Never: ExpirationTimeOpaque = 1;
32 -// Idle is slightly higher priority than Never. It must completely finish in
33 -// order to be consistent.
34 -export const Idle: ExpirationTimeOpaque = 2;
35 -// Continuous Hydration is slightly higher than Idle and is used to increase
36 -// priority of hover targets.
37 -export const ContinuousHydration: ExpirationTimeOpaque = 3;
38 -export const LongTransition: ExpirationTimeOpaque = 49999;
39 -export const ShortTransition: ExpirationTimeOpaque = 99999;
40 -export const DefaultUpdateTime: ExpirationTimeOpaque = 1073741296;
41 -export const UserBlockingUpdateTime: ExpirationTimeOpaque = 1073741761;
42 -export const Sync: ExpirationTimeOpaque = MAX_SIGNED_31_BIT_INT;
43 -export const Batched: ExpirationTimeOpaque = Sync - 1;
44 -
45 -// Accounts for -1 trick to bump updates into a different batch
46 -const ADJUSTMENT_OFFSET = 5;
47 -
48 -export function inferPriorityFromExpirationTime(
49 - expirationTime: ExpirationTimeOpaque,
50 -): ReactPriorityLevel {
51 - if (expirationTime >= Batched - ADJUSTMENT_OFFSET) {
52 - return ImmediatePriority;
53 - }
54 - if (expirationTime >= UserBlockingUpdateTime - ADJUSTMENT_OFFSET) {
55 - return UserBlockingPriority;
56 - }
57 - if (expirationTime >= LongTransition - ADJUSTMENT_OFFSET) {
58 - return NormalPriority;
59 - }
60 -
61 - // TODO: Handle LowPriority. Maybe should give it NormalPriority since Idle is
62 - // very agressively deprioritized.
63 -
64 - // Assume anything lower has idle priority
65 - return IdlePriority;
66 -}
67 -
68 -export function isSameOrHigherPriority(
69 - a: ExpirationTimeOpaque,
70 - b: ExpirationTimeOpaque,
71 -) {
72 - return a >= b;
73 -}
74 -
75 -export function isSameExpirationTime(
76 - a: ExpirationTimeOpaque,
77 - b: ExpirationTimeOpaque,
78 -) {
79 - return a === b;
80 -}
81 -
82 -export function bumpPriorityHigher(
83 - a: ExpirationTimeOpaque,
84 -): ExpirationTimeOpaque {
85 - return a + 1;
86 -}
87 -
88 -export function bumpPriorityLower(
89 - a: ExpirationTimeOpaque,
90 -): ExpirationTimeOpaque {
91 - return a - 1;
92 -}
packages/react-reconciler/src/ReactFiberHooks.new.js
+66 -79
@@ -16,7 +16,7 @@ import type {
16 ReactEventResponderListener,
17 } from 'shared/ReactTypes';
18 import type {Fiber, Dispatcher} from './ReactInternalTypes';
19 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
19 +import type {Lanes, Lane} from './ReactFiberLane';
20 import type {HookEffectTag} from './ReactHookEffectTags';
21 import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
22 import type {ReactPriorityLevel} from './ReactInternalTypes';
@@ -25,14 +25,16 @@ import type {OpaqueIDType} from './ReactFiberHostConfig';
25
26 import ReactSharedInternals from 'shared/ReactSharedInternals';
27
28 -import {markRootExpiredAtTime} from './ReactFiberRoot.new';
29 -import {
30 - NoWork,
31 - Sync,
32 - isSameOrHigherPriority,
33 - isSameExpirationTime,
34 -} from './ReactFiberExpirationTime.new';
28 import {NoMode, BlockingMode} from './ReactTypeOfMode';
29 +import {
30 + NoLane,
31 + NoLanes,
32 + isSubsetOfLanes,
33 + mergeLanes,
34 + removeLanes,
35 + markRootExpired,
36 + markRootMutableRead,
37 +} from './ReactFiberLane';
38 import {readContext} from './ReactFiberNewContext.new';
39 import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
40 import {
@@ -47,13 +49,13 @@ import {
49 import {
50 getWorkInProgressRoot,
51 scheduleUpdateOnFiber,
50 - requestUpdateExpirationTime,
52 + requestUpdateLane,
53 requestEventTime,
54 warnIfNotCurrentlyActingEffectsInDEV,
55 warnIfNotCurrentlyActingUpdatesInDev,
56 warnIfNotScopedWithMatchingAct,
57 markRenderEventTimeAndConfig,
56 - markUnprocessedUpdateTime,
58 + markSkippedUpdateLanes,
59 } from './ReactFiberWorkLoop.new';
60
61 import invariant from 'shared/invariant';
@@ -74,10 +76,8 @@ import {
76 makeOpaqueHydratingObject,
77 } from './ReactFiberHostConfig';
78 import {
77 - getLastPendingExpirationTime,
79 getWorkInProgressVersion,
80 markSourceAsDirty,
80 - setPendingExpirationTime,
81 setWorkInProgressVersion,
82 warnAboutMultipleRenderersDEV,
83 } from './ReactMutableSource.new';
@@ -89,7 +89,7 @@ type Update<S, A> = {|
89 // TODO: Temporary field. Will remove this by storing a map of
90 // transition -> start time on the root.
91 eventTime: number,
92 - expirationTime: ExpirationTimeOpaque,
92 + lane: Lane,
93 suspenseConfig: null | SuspenseConfig,
94 action: A,
95 eagerReducer: ((S, A) => S) | null,
@@ -156,7 +156,7 @@ type BasicStateAction<S> = (S => S) | S;
156 type Dispatch<A> = A => void;
157
158 // These are set right before calling the component.
159 -let renderExpirationTime: ExpirationTimeOpaque = NoWork;
159 +let renderLanes: Lanes = NoLanes;
160 // The work-in-progress fiber. I've named it differently to distinguish it from
161 // the work-in-progress hook.
162 let currentlyRenderingFiber: Fiber = (null: any);
@@ -347,9 +347,9 @@ export function renderWithHooks<Props, SecondArg>(
347 Component: (p: Props, arg: SecondArg) => any,
348 props: Props,
349 secondArg: SecondArg,
350 - nextRenderExpirationTime: ExpirationTimeOpaque,
350 + nextRenderLanes: Lanes,
351 ): any {
352 - renderExpirationTime = nextRenderExpirationTime;
352 + renderLanes = nextRenderLanes;
353 currentlyRenderingFiber = workInProgress;
354
355 if (__DEV__) {
@@ -365,7 +365,7 @@ export function renderWithHooks<Props, SecondArg>(
365
366 workInProgress.memoizedState = null;
367 workInProgress.updateQueue = null;
368 - workInProgress.expirationTime_opaque = NoWork;
368 + workInProgress.lanes = NoLanes;
369
370 // The following should have already been reset
371 // currentHook = null;
@@ -454,7 +454,7 @@ export function renderWithHooks<Props, SecondArg>(
454 const didRenderTooFewHooks =
455 currentHook !== null && currentHook.next !== null;
456
457 - renderExpirationTime = NoWork;
457 + renderLanes = NoLanes;
458 currentlyRenderingFiber = (null: any);
459
460 currentHook = null;
@@ -480,13 +480,11 @@ export function renderWithHooks<Props, SecondArg>(
480 export function bailoutHooks(
481 current: Fiber,
482 workInProgress: Fiber,
483 - expirationTime: ExpirationTimeOpaque,
483 + lanes: Lanes,
484 ) {
485 workInProgress.updateQueue = current.updateQueue;
486 workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);
487 - if (isSameOrHigherPriority(expirationTime, current.expirationTime_opaque)) {
488 - current.expirationTime_opaque = NoWork;
489 - }
487 + current.lanes = removeLanes(current.lanes, lanes);
488 }
489
490 export function resetHooksAfterThrow(): void {
@@ -514,7 +512,7 @@ export function resetHooksAfterThrow(): void {
512 didScheduleRenderPhaseUpdate = false;
513 }
514
517 - renderExpirationTime = NoWork;
515 + renderLanes = NoLanes;
516 currentlyRenderingFiber = (null: any);
517
518 currentHook = null;
@@ -708,15 +706,15 @@ function updateReducer<S, I, A>(
706 let update = first;
707 do {
708 const suspenseConfig = update.suspenseConfig;
711 - const updateExpirationTime = update.expirationTime;
709 + const updateLane = update.lane;
710 const updateEventTime = update.eventTime;
713 - if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
711 + if (!isSubsetOfLanes(renderLanes, updateLane)) {
712 // Priority is insufficient. Skip this update. If this is the first
713 // skipped update, the previous update/state is the new base
714 // update/state.
715 const clone: Update<S, A> = {
716 eventTime: updateEventTime,
719 - expirationTime: updateExpirationTime,
717 + lane: updateLane,
718 suspenseConfig: suspenseConfig,
719 action: update.action,
720 eagerReducer: update.eagerReducer,
@@ -730,22 +728,23 @@ function updateReducer<S, I, A>(
728 newBaseQueueLast = newBaseQueueLast.next = clone;
729 }
730 // Update the remaining priority in the queue.
733 - if (
734 - !isSameOrHigherPriority(
735 - currentlyRenderingFiber.expirationTime_opaque,
736 - updateExpirationTime,
737 - )
738 - ) {
739 - currentlyRenderingFiber.expirationTime_opaque = updateExpirationTime;
740 - markUnprocessedUpdateTime(updateExpirationTime);
741 - }
731 + // TODO: Don't need to accumulate this. Instead, we can remove
732 + // renderLanes from the original lanes.
733 + currentlyRenderingFiber.lanes = mergeLanes(
734 + currentlyRenderingFiber.lanes,
735 + updateLane,
736 + );
737 + markSkippedUpdateLanes(updateLane);
738 } else {
739 // This update does have sufficient priority.
740
741 if (newBaseQueueLast !== null) {
742 const clone: Update<S, A> = {
743 eventTime: updateEventTime,
748 - expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
744 + // This update is going to be committed so we never want uncommit
745 + // it. Using NoLane works because 0 is a subset of all bitmasks, so
746 + // this will never be skipped by the check above.
747 + lane: NoLane,
748 suspenseConfig: update.suspenseConfig,
749 action: update.action,
750 eagerReducer: update.eagerReducer,
@@ -882,26 +881,28 @@ function readFromUnsubcribedMutableSource<Source, Snapshot>(
881 // we can use it alone to determine if we can safely read from the source.
882 const currentRenderVersion = getWorkInProgressVersion(source);
883 if (currentRenderVersion !== null) {
884 + // It's safe to read if the store hasn't been mutated since the last time
885 + // we read something.
886 isSafeToReadFromSource = currentRenderVersion === version;
887 } else {
887 - // If there's no version, then we should fallback to checking the update time.
888 - const pendingExpirationTime = getLastPendingExpirationTime(root);
889 -
890 - if (
891 - isSameExpirationTime(
892 - pendingExpirationTime,
893 - (NoWork: ExpirationTimeOpaque),
894 - )
895 - ) {
896 - isSafeToReadFromSource = true;
897 - } else {
898 - // If the source has pending updates, we can use the current render's expiration
899 - // time to determine if it's safe to read again from the source.
900 - isSafeToReadFromSource = isSameOrHigherPriority(
901 - pendingExpirationTime,
902 - renderExpirationTime,
903 - );
904 - }
888 + // If there's no version, then this is the first time we've read from the
889 + // source during the current render pass, so we need to do a bit more work.
890 + // What we need to determine is if there are any hooks that already
891 + // subscribed to the source, and if so, whether there are any pending
892 + // mutations that haven't been synchronized yet.
893 + //
894 + // If there are no pending mutations, then `root.mutableReadLanes` will be
895 + // empty, and we know we can safely read.
896 + //
897 + // If there *are* pending mutations, we may still be able to safely read
898 + // if the currently rendering lanes are inclusive of the pending mutation
899 + // lanes, since that guarantees that the value we're about to read from
900 + // the source is consistent with the values that we read during the most
901 + // recent mutation.
902 + isSafeToReadFromSource = isSubsetOfLanes(
903 + renderLanes,
904 + root.mutableReadLanes,
905 + );
906
907 if (isSafeToReadFromSource) {
908 // If it's safe to read from this source during the current render,
@@ -994,19 +995,16 @@ function useMutableSource<Source, Snapshot>(
995 setSnapshot(maybeNewSnapshot);
996
997 const suspenseConfig = requestCurrentSuspenseConfig();
997 - const expirationTime = requestUpdateExpirationTime(
998 - fiber,
999 - suspenseConfig,
1000 - );
1001 - setPendingExpirationTime(root, expirationTime);
998 + const lane = requestUpdateLane(fiber, suspenseConfig);
999 + markRootMutableRead(root, lane);
1000
1001 // If the source mutated between render and now,
1002 // there may be state updates already scheduled from the old getSnapshot.
1003 // Those updates should not commit without this value.
1004 // There is no mechanism currently to associate these updates though,
1005 // so for now we fall back to synchronously flushing all pending updates.
1008 - // TODO: Improve this later.
1009 - markRootExpiredAtTime(root, getLastPendingExpirationTime(root));
1006 + // TODO: This should entangle the lanes instead of expiring everything.
1007 + markRootExpired(root, root.mutableReadLanes);
1008 }
1009 }
1010 }, [getSnapshot, source, subscribe]);
@@ -1022,12 +1020,9 @@ function useMutableSource<Source, Snapshot>(
1020
1021 // Record a pending mutable source update with the same expiration time.
1022 const suspenseConfig = requestCurrentSuspenseConfig();
1025 - const expirationTime = requestUpdateExpirationTime(
1026 - fiber,
1027 - suspenseConfig,
1028 - );
1023 + const lane = requestUpdateLane(fiber, suspenseConfig);
1024
1030 - setPendingExpirationTime(root, expirationTime);
1025 + markRootMutableRead(root, lane);
1026 } catch (error) {
1027 // A selector might throw after a source mutation.
1028 // e.g. it might try to read from a part of the store that no longer exists.
@@ -1646,11 +1641,11 @@ function dispatchAction<S, A>(
1641
1642 const eventTime = requestEventTime();
1643 const suspenseConfig = requestCurrentSuspenseConfig();
1649 - const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
1644 + const lane = requestUpdateLane(fiber, suspenseConfig);
1645
1646 const update: Update<S, A> = {
1647 eventTime,
1653 - expirationTime,
1648 + lane,
1649 suspenseConfig,
1650 action,
1651 eagerReducer: null,
@@ -1678,18 +1673,10 @@ function dispatchAction<S, A>(
1673 // queue -> linked list of updates. After this render pass, we'll restart
1674 // and apply the stashed updates on top of the work-in-progress hook.
1675 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
1681 - update.expirationTime = renderExpirationTime;
1676 } else {
1677 if (
1684 - isSameExpirationTime(
1685 - fiber.expirationTime_opaque,
1686 - (NoWork: ExpirationTimeOpaque),
1687 - ) &&
1688 - (alternate === null ||
1689 - isSameExpirationTime(
1690 - alternate.expirationTime_opaque,
1691 - (NoWork: ExpirationTimeOpaque),
1692 - ))
1678 + fiber.lanes === NoLanes &&
1679 + (alternate === null || alternate.lanes === NoLanes)
1680 ) {
1681 // The queue is currently empty, which means we can eagerly compute the
1682 // next state before entering the render phase. If the new state is the
@@ -1733,7 +1720,7 @@ function dispatchAction<S, A>(
1720 warnIfNotCurrentlyActingUpdatesInDev(fiber);
1721 }
1722 }
1736 - scheduleUpdateOnFiber(fiber, expirationTime);
1723 + scheduleUpdateOnFiber(fiber, lane);
1724 }
1725 }
1726
packages/react-reconciler/src/ReactFiberHotReloading.new.js
+2 -3
@@ -12,7 +12,6 @@ import type {Fiber} from './ReactInternalTypes';
12 import type {FiberRoot} from './ReactInternalTypes';
13 import type {Instance} from './ReactFiberHostConfig';
14 import type {ReactNodeList} from 'shared/ReactTypes';
15 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
15
16 import {
17 flushSync,
@@ -21,7 +20,7 @@ import {
20 } from './ReactFiberWorkLoop.new';
21 import {updateContainer, syncUpdates} from './ReactFiberReconciler.new';
22 import {emptyContextObject} from './ReactFiberContext.new';
24 -import {Sync} from './ReactFiberExpirationTime.new';
23 +import {SyncLane} from './ReactFiberLane';
24 import {
25 ClassComponent,
26 FunctionComponent,
@@ -320,7 +319,7 @@ function scheduleFibersWithFamiliesRecursively(
319 fiber._debugNeedsRemount = true;
320 }
321 if (needsRemount || needsRender) {
323 - scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
322 + scheduleUpdateOnFiber(fiber, SyncLane);
323 }
324 if (child !== null && !needsRemount) {
325 scheduleFibersWithFamiliesRecursively(
packages/react-reconciler/src/ReactFiberHydrationContext.new.js
+2 -2
@@ -55,7 +55,7 @@ import {
55 didNotFindHydratableSuspenseInstance,
56 } from './ReactFiberHostConfig';
57 import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
58 -import {Never} from './ReactFiberExpirationTime.new';
58 +import {OffscreenLane} from './ReactFiberLane';
59
60 // The deepest Fiber on the stack involved in a hydration context.
61 // This may have been an insertion or a hydration.
@@ -231,7 +231,7 @@ function tryHydrate(fiber, nextInstance) {
231 if (suspenseInstance !== null) {
232 const suspenseState: SuspenseState = {
233 dehydrated: suspenseInstance,
234 - retryTime: Never,
234 + retryLane: OffscreenLane,
235 };
236 fiber.memoizedState = suspenseState;
237 // Store the dehydrated fragment as a child fiber.
packages/react-reconciler/src/ReactFiberLane.js
+679 -13
@@ -7,16 +7,682 @@
7 * @flow
8 */
9
10 -export opaque type LanePriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
11 -
12 -export const SyncLanePriority: LanePriority = 10;
13 -export const SyncBatchedLanePriority: LanePriority = 9;
14 -export const InputDiscreteLanePriority: LanePriority = 8;
15 -export const InputContinuousLanePriority: LanePriority = 7;
16 -export const DefaultLanePriority: LanePriority = 6;
17 -export const TransitionShortLanePriority: LanePriority = 5;
18 -export const TransitionLongLanePriority: LanePriority = 4;
19 -export const HydrationContinuousLanePriority: LanePriority = 3;
20 -export const IdleLanePriority: LanePriority = 2;
21 -export const OffscreenLanePriority: LanePriority = 1;
22 -export const NoLanePriority: LanePriority = 0;
10 +import type {FiberRoot, ReactPriorityLevel} from './ReactInternalTypes';
11 +
12 +export opaque type LanePriority =
13 + | 0
14 + | 1
15 + | 2
16 + | 3
17 + | 4
18 + | 5
19 + | 6
20 + | 7
21 + | 8
22 + | 9
23 + | 10
24 + | 11
25 + | 12
26 + | 13
27 + | 14
28 + | 15
29 + | 16;
30 +export opaque type Lanes = number;
31 +export opaque type Lane = number;
32 +
33 +import invariant from 'shared/invariant';
34 +
35 +import {
36 + ImmediatePriority as ImmediateSchedulerPriority,
37 + UserBlockingPriority as UserBlockingSchedulerPriority,
38 + NormalPriority as NormalSchedulerPriority,
39 + LowPriority as LowSchedulerPriority,
40 + IdlePriority as IdleSchedulerPriority,
41 + NoPriority as NoSchedulerPriority,
42 +} from './SchedulerWithReactIntegration.new';
43 +
44 +export const SyncLanePriority: LanePriority = 16;
45 +const SyncBatchedLanePriority: LanePriority = 15;
46 +
47 +const InputDiscreteHydrationLanePriority: LanePriority = 14;
48 +export const InputDiscreteLanePriority: LanePriority = 13;
49 +
50 +const InputContinuousHydrationLanePriority: LanePriority = 12;
51 +const InputContinuousLanePriority: LanePriority = 11;
52 +
53 +const DefaultHydrationLanePriority: LanePriority = 10;
54 +const DefaultLanePriority: LanePriority = 9;
55 +
56 +const TransitionShortHydrationLanePriority: LanePriority = 8;
57 +export const TransitionShortLanePriority: LanePriority = 7;
58 +
59 +const TransitionLongHydrationLanePriority: LanePriority = 6;
60 +export const TransitionLongLanePriority: LanePriority = 5;
61 +
62 +const SelectiveHydrationLanePriority: LanePriority = 4;
63 +
64 +const IdleHydrationLanePriority: LanePriority = 3;
65 +const IdleLanePriority: LanePriority = 2;
66 +
67 +const OffscreenLanePriority: LanePriority = 1;
68 +
69 +const NoLanePriority: LanePriority = 0;
70 +
71 +const TotalLanes = 31;
72 +
73 +export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
74 +export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
75 +
76 +export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
77 +const SyncUpdateRangeEnd = 1;
78 +export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
79 +const SyncBatchedUpdateRangeEnd = 2;
80 +
81 +export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
82 +const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011100;
83 +const InputDiscreteUpdateRangeStart = 3;
84 +const InputDiscreteUpdateRangeEnd = 5;
85 +
86 +const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
87 +const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011100000;
88 +const InputContinuousUpdateRangeStart = 6;
89 +const InputContinuousUpdateRangeEnd = 8;
90 +
91 +export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
92 +const DefaultLanes: Lanes = /* */ 0b0000000000000000011111100000000;
93 +const DefaultUpdateRangeStart = 9;
94 +const DefaultUpdateRangeEnd = 14;
95 +
96 +const TransitionShortHydrationLane: Lane = /* */ 0b0000000000000000100000000000000;
97 +const TransitionShortLanes: Lanes = /* */ 0b0000000000011111100000000000000;
98 +const TransitionShortUpdateRangeStart = 15;
99 +const TransitionShortUpdateRangeEnd = 20;
100 +
101 +const TransitionLongHydrationLane: Lane = /* */ 0b0000000000100000000000000000000;
102 +const TransitionLongLanes: Lanes = /* */ 0b0000011111100000000000000000000;
103 +const TransitionLongUpdateRangeStart = 21;
104 +const TransitionLongUpdateRangeEnd = 26;
105 +
106 +export const SelectiveHydrationLane: Lane = /* */ 0b0000110000000000000000000000000;
107 +const SelectiveHydrationRangeEnd = 27;
108 +
109 +// Includes all non-Idle updates
110 +const UpdateRangeEnd = 27;
111 +const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
112 +
113 +export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
114 +const IdleLanes: Lanes = /* */ 0b0111000000000000000000000000000;
115 +const IdleUpdateRangeStart = 28;
116 +const IdleUpdateRangeEnd = 30;
117 +
118 +export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
119 +
120 +// "Registers" used to "return" multiple values
121 +// Used by getHighestPriorityLanes and getNextLanes:
122 +let return_highestLanePriority: LanePriority = DefaultLanePriority;
123 +let return_updateRangeEnd: number = -1;
124 +
125 +function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
126 + if ((SyncLane & lanes) !== NoLanes) {
127 + return_highestLanePriority = SyncLanePriority;
128 + return_updateRangeEnd = SyncUpdateRangeEnd;
129 + return SyncLane;
130 + }
131 + if ((SyncBatchedLane & lanes) !== NoLanes) {
132 + return_highestLanePriority = SyncBatchedLanePriority;
133 + return_updateRangeEnd = SyncBatchedUpdateRangeEnd;
134 + return SyncBatchedLane;
135 + }
136 + const inputDiscreteLanes = InputDiscreteLanes & lanes;
137 + if (inputDiscreteLanes !== NoLanes) {
138 + if (inputDiscreteLanes & InputDiscreteHydrationLane) {
139 + return_highestLanePriority = InputDiscreteHydrationLanePriority;
140 + return_updateRangeEnd = InputDiscreteUpdateRangeStart;
141 + return InputDiscreteHydrationLane;
142 + } else {
143 + return_highestLanePriority = InputDiscreteLanePriority;
144 + return_updateRangeEnd = InputDiscreteUpdateRangeEnd;
145 + return inputDiscreteLanes;
146 + }
147 + }
148 + const inputContinuousLanes = InputContinuousLanes & lanes;
149 + if (inputContinuousLanes !== NoLanes) {
150 + if (inputContinuousLanes & InputContinuousHydrationLane) {
151 + return_highestLanePriority = InputContinuousHydrationLanePriority;
152 + return_updateRangeEnd = InputContinuousUpdateRangeStart;
153 + return InputContinuousHydrationLane;
154 + } else {
155 + return_highestLanePriority = InputContinuousLanePriority;
156 + return_updateRangeEnd = InputContinuousUpdateRangeEnd;
157 + return inputContinuousLanes;
158 + }
159 + }
160 + const defaultLanes = DefaultLanes & lanes;
161 + if (defaultLanes !== NoLanes) {
162 + if (defaultLanes & DefaultHydrationLane) {
163 + return_highestLanePriority = DefaultHydrationLanePriority;
164 + return_updateRangeEnd = DefaultUpdateRangeStart;
165 + return DefaultHydrationLane;
166 + } else {
167 + return_highestLanePriority = DefaultLanePriority;
168 + return_updateRangeEnd = DefaultUpdateRangeEnd;
169 + return defaultLanes;
170 + }
171 + }
172 + const transitionShortLanes = TransitionShortLanes & lanes;
173 + if (transitionShortLanes !== NoLanes) {
174 + if (transitionShortLanes & TransitionShortHydrationLane) {
175 + return_highestLanePriority = TransitionShortHydrationLanePriority;
176 + return_updateRangeEnd = TransitionShortUpdateRangeStart;
177 + return TransitionShortHydrationLane;
178 + } else {
179 + return_highestLanePriority = TransitionShortLanePriority;
180 + return_updateRangeEnd = TransitionShortUpdateRangeEnd;
181 + return transitionShortLanes;
182 + }
183 + }
184 + const transitionLongLanes = TransitionLongLanes & lanes;
185 + if (transitionLongLanes !== NoLanes) {
186 + if (transitionLongLanes & TransitionLongHydrationLane) {
187 + return_highestLanePriority = TransitionLongHydrationLanePriority;
188 + return_updateRangeEnd = TransitionLongUpdateRangeStart;
189 + return TransitionLongHydrationLane;
190 + } else {
191 + return_highestLanePriority = TransitionLongLanePriority;
192 + return_updateRangeEnd = TransitionLongUpdateRangeEnd;
193 + return transitionLongLanes;
194 + }
195 + }
196 + if (lanes & SelectiveHydrationLane) {
197 + return_highestLanePriority = SelectiveHydrationLanePriority;
198 + return_updateRangeEnd = SelectiveHydrationRangeEnd;
199 + return SelectiveHydrationLane;
200 + }
201 + const idleLanes = IdleLanes & lanes;
202 + if (idleLanes !== NoLanes) {
203 + if (idleLanes & IdleHydrationLane) {
204 + return_highestLanePriority = IdleHydrationLanePriority;
205 + return_updateRangeEnd = IdleUpdateRangeStart;
206 + return IdleHydrationLane;
207 + } else {
208 + return_updateRangeEnd = IdleUpdateRangeEnd;
209 + return idleLanes;
210 + }
211 + }
212 + if ((OffscreenLane & lanes) !== NoLanes) {
213 + return_highestLanePriority = OffscreenLanePriority;
214 + return_updateRangeEnd = TotalLanes;
215 + return OffscreenLane;
216 + }
217 + if (__DEV__) {
218 + console.error('Should have found matching lanes. This is a bug in React.');
219 + }
220 + // This shouldn't be reachable, but as a fallback, return the entire bitmask.
221 + return_highestLanePriority = DefaultLanePriority;
222 + return_updateRangeEnd = DefaultUpdateRangeEnd;
223 + return lanes;
224 +}
225 +
226 +export function schedulerPriorityToLanePriority(
227 + schedulerPriorityLevel: ReactPriorityLevel,
228 +): LanePriority {
229 + switch (schedulerPriorityLevel) {
230 + case ImmediateSchedulerPriority:
231 + return SyncLanePriority;
232 + case UserBlockingSchedulerPriority:
233 + return InputContinuousLanePriority;
234 + case NormalSchedulerPriority:
235 + case LowSchedulerPriority:
236 + // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
237 + return DefaultLanePriority;
238 + case IdleSchedulerPriority:
239 + return IdleLanePriority;
240 + default:
241 + return NoLanePriority;
242 + }
243 +}
244 +
245 +export function lanePriorityToSchedulerPriority(
246 + lanePriority: LanePriority,
247 +): ReactPriorityLevel {
248 + switch (lanePriority) {
249 + case SyncLanePriority:
250 + case SyncBatchedLanePriority:
251 + return ImmediateSchedulerPriority;
252 + case InputDiscreteHydrationLanePriority:
253 + case InputDiscreteLanePriority:
254 + case InputContinuousHydrationLanePriority:
255 + case InputContinuousLanePriority:
256 + return UserBlockingSchedulerPriority;
257 + case DefaultHydrationLanePriority:
258 + case DefaultLanePriority:
259 + case TransitionShortHydrationLanePriority:
260 + case TransitionShortLanePriority:
261 + case TransitionLongHydrationLanePriority:
262 + case TransitionLongLanePriority:
263 + case SelectiveHydrationLanePriority:
264 + return NormalSchedulerPriority;
265 + case IdleHydrationLanePriority:
266 + case IdleLanePriority:
267 + case OffscreenLanePriority:
268 + return IdleSchedulerPriority;
269 + case NoLanePriority:
270 + return NoSchedulerPriority;
271 + default:
272 + invariant(
273 + false,
274 + 'Invalid update priority: %s. This is a bug in React.',
275 + lanePriority,
276 + );
277 + }
278 +}
279 +
280 +export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
281 + // Early bailout if there's no pending work left.
282 + const pendingLanes = root.pendingLanes;
283 + if (pendingLanes === NoLanes) {
284 + return_highestLanePriority = NoLanePriority;
285 + return NoLanes;
286 + }
287 +
288 + let nextLanes = NoLanes;
289 + let nextLanePriority = NoLanePriority;
290 + let equalOrHigherPriorityLanes = NoLanes;
291 +
292 + const expiredLanes = root.expiredLanes;
293 + const suspendedLanes = root.suspendedLanes;
294 + const pingedLanes = root.pingedLanes;
295 +
296 + // Check if any work has expired.
297 + if (expiredLanes !== NoLanes) {
298 + nextLanes = expiredLanes;
299 + nextLanePriority = return_highestLanePriority = SyncLanePriority;
300 + equalOrHigherPriorityLanes = (getLowestPriorityLane(nextLanes) << 1) - 1;
301 + } else {
302 + // Do not work on any idle work until all the non-idle work has finished,
303 + // even if the work is suspended.
304 + const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
305 + if (nonIdlePendingLanes !== NoLanes) {
306 + const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
307 + if (nonIdleUnblockedLanes !== NoLanes) {
308 + nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
309 + nextLanePriority = return_highestLanePriority;
310 + equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
311 + } else {
312 + const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
313 + if (nonIdlePingedLanes !== NoLanes) {
314 + nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
315 + nextLanePriority = return_highestLanePriority;
316 + equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
317 + }
318 + }
319 + } else {
320 + // The only remaining work is Idle.
321 + const unblockedLanes = pendingLanes & ~suspendedLanes;
322 + if (unblockedLanes !== NoLanes) {
323 + nextLanes = getHighestPriorityLanes(unblockedLanes);
324 + nextLanePriority = return_highestLanePriority;
325 + equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
326 + } else {
327 + if (pingedLanes !== NoLanes) {
328 + nextLanes = getHighestPriorityLanes(pingedLanes);
329 + nextLanePriority = return_highestLanePriority;
330 + equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
331 + }
332 + }
333 + }
334 + }
335 +
336 + if (nextLanes === NoLanes) {
337 + // This should only be reachable if we're suspended
338 + // TODO: Consider warning in this path if a fallback timer is not scheduled.
339 + return NoLanes;
340 + }
341 +
342 + // If there are higher priority lanes, we'll include them even if they
343 + // are suspended.
344 + nextLanes = pendingLanes & equalOrHigherPriorityLanes;
345 +
346 + // If we're already in the middle of a render, switching lanes will interrupt
347 + // it and we'll lose our progress. We should only do this if the new lanes are
348 + // higher priority.
349 + if (
350 + wipLanes !== NoLanes &&
351 + wipLanes !== nextLanes &&
352 + // If we already suspended with a delay, then interrupting is fine. Don't
353 + // bother waiting until the root is complete.
354 + (wipLanes & suspendedLanes) === NoLanes
355 + ) {
356 + getHighestPriorityLanes(wipLanes);
357 + const wipLanePriority = return_highestLanePriority;
358 + if (nextLanePriority <= wipLanePriority) {
359 + return wipLanes;
360 + } else {
361 + return_highestLanePriority = nextLanePriority;
362 + }
363 + }
364 +
365 + return nextLanes;
366 +}
367 +
368 +// This returns the highest priority pending lanes regardless of whether they
369 +// are suspended.
370 +export function getHighestPriorityPendingLanes(root: FiberRoot) {
371 + return getHighestPriorityLanes(root.pendingLanes);
372 +}
373 +
374 +export function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes {
375 + const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
376 + if (everythingButOffscreen !== NoLanes) {
377 + return everythingButOffscreen;
378 + }
379 + if (everythingButOffscreen & OffscreenLane) {
380 + return OffscreenLane;
381 + }
382 + return NoLanes;
383 +}
384 +
385 +export function returnNextLanesPriority() {
386 + return return_highestLanePriority;
387 +}
388 +export function hasUpdatePriority(lanes: Lanes) {
389 + return (lanes & NonIdleLanes) !== NoLanes;
390 +}
391 +
392 +// To ensure consistency across multiple updates in the same event, this should
393 +// be a pure function, so that it always returns the same lane for given inputs.
394 +export function findUpdateLane(
395 + lanePriority: LanePriority,
396 + wipLanes: Lanes,
397 +): Lane {
398 + switch (lanePriority) {
399 + case NoLanePriority:
400 + break;
401 + case SyncLanePriority:
402 + return SyncLane;
403 + case SyncBatchedLanePriority:
404 + return SyncBatchedLane;
405 + case InputDiscreteLanePriority: {
406 + let lane = findLane(
407 + InputDiscreteUpdateRangeStart,
408 + UpdateRangeEnd,
409 + wipLanes,
410 + );
411 + if (lane === NoLane) {
412 + lane = InputDiscreteHydrationLane;
413 + }
414 + return lane;
415 + }
416 + case InputContinuousLanePriority: {
417 + let lane = findLane(
418 + InputContinuousUpdateRangeStart,
419 + UpdateRangeEnd,
420 + wipLanes,
421 + );
422 + if (lane === NoLane) {
423 + lane = InputContinuousHydrationLane;
424 + }
425 + return lane;
426 + }
427 + case DefaultLanePriority: {
428 + let lane = findLane(DefaultUpdateRangeStart, UpdateRangeEnd, wipLanes);
429 + if (lane === NoLane) {
430 + lane = DefaultHydrationLane;
431 + }
432 + return lane;
433 + }
434 + case TransitionShortLanePriority:
435 + case TransitionLongLanePriority:
436 + // Should be handled by findTransitionLane instead
437 + break;
438 + case IdleLanePriority:
439 + let lane = findLane(IdleUpdateRangeStart, IdleUpdateRangeEnd, IdleLanes);
440 + if (lane === NoLane) {
441 + lane = IdleHydrationLane;
442 + }
443 + return lane;
444 + default:
445 + // The remaining priorities are not valid for updates
446 + break;
447 + }
448 + invariant(
449 + false,
450 + 'Invalid update priority: %s. This is a bug in React.',
451 + lanePriority,
452 + );
453 +}
454 +
455 +// To ensure consistency across multiple updates in the same event, this should
456 +// be pure function, so that it always returns the same lane for given inputs.
457 +export function findTransitionLane(
458 + lanePriority: LanePriority,
459 + wipLanes: Lanes,
460 + pendingLanes: Lanes,
461 +): Lane {
462 + if (lanePriority === TransitionShortLanePriority) {
463 + let lane = findLane(
464 + TransitionShortUpdateRangeStart,
465 + TransitionShortUpdateRangeEnd,
466 + wipLanes | pendingLanes,
467 + );
468 + if (lane === NoLane) {
469 + lane = findLane(
470 + TransitionShortUpdateRangeStart,
471 + TransitionShortUpdateRangeEnd,
472 + wipLanes,
473 + );
474 + if (lane === NoLane) {
475 + lane = TransitionShortHydrationLane;
476 + }
477 + }
478 + return lane;
479 + }
480 + if (lanePriority === TransitionLongLanePriority) {
481 + let lane = findLane(
482 + TransitionLongUpdateRangeStart,
483 + TransitionLongUpdateRangeEnd,
484 + wipLanes | pendingLanes,
485 + );
486 + if (lane === NoLane) {
487 + lane = findLane(
488 + TransitionLongUpdateRangeStart,
489 + TransitionLongUpdateRangeEnd,
490 + wipLanes,
491 + );
492 + if (lane === NoLane) {
493 + lane = TransitionLongHydrationLane;
494 + }
495 + }
496 + return lane;
497 + }
498 + invariant(
499 + false,
500 + 'Invalid transition priority: %s. This is a bug in React.',
501 + lanePriority,
502 + );
503 +}
504 +
505 +function findLane(start, end, skipLanes) {
506 + // This finds the first bit between the `start` and `end` positions that isn't
507 + // in `skipLanes`.
508 + // TODO: This will always favor the rightmost bits. That's usually fine
509 + // because any bit that's pending will be part of `skipLanes`, so we'll do our
510 + // best to avoid accidental entanglement. However, lanes that are pending
511 + // inside an Offscreen tree aren't considered "pending" at the root level. So
512 + // they aren't included in `skipLanes`. So we should try not to favor any
513 + // particular part of the range, perhaps by incrementing an offset for each
514 + // distinct event. Must be the same within a single event, though.
515 + const bitsInRange = ((1 << (end - start)) - 1) << start;
516 + const possibleBits = bitsInRange & ~skipLanes;
517 + const leastSignificantBit = possibleBits & -possibleBits;
518 + return leastSignificantBit;
519 +}
520 +
521 +function getLowestPriorityLane(lanes: Lanes): Lane {
522 + // This finds the most significant non-zero bit.
523 + const index = 31 - clz32(lanes);
524 + return index < 0 ? NoLanes : 1 << index;
525 +}
526 +
527 +export function pickArbitraryLane(lanes: Lanes): Lane {
528 + return getLowestPriorityLane(lanes);
529 +}
530 +
531 +export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
532 + return (a & b) !== NoLanes;
533 +}
534 +
535 +export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
536 + return (set & subset) === subset;
537 +}
538 +
539 +export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
540 + return a | b;
541 +}
542 +
543 +export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
544 + return set & ~subset;
545 +}
546 +
547 +// Seems redundant, but it changes the type from a single lane (used for
548 +// updates) to a group of lanes (used for flushing work).
549 +export function laneToLanes(lane: Lane): Lanes {
550 + return lane;
551 +}
552 +
553 +export function higherPriorityLane(a: Lane, b: Lane) {
554 + // This works because the bit ranges decrease in priority as you go left.
555 + return a !== NoLane && a < b ? a : b;
556 +}
557 +
558 +export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
559 + root.pendingLanes |= updateLane;
560 +
561 + // TODO: Theoretically, any update to any lane can unblock any other lane. But
562 + // it's not practical to try every single possible combination. We need a
563 + // heuristic to decide which lanes to attempt to render, and in which batches.
564 + // For now, we use the same heuristic as in the old ExpirationTimes model:
565 + // retry any lane at equal or lower priority, but don't try updates at higher
566 + // priority without also including the lower priority updates. This works well
567 + // when considering updates across different priority levels, but isn't
568 + // sufficient for updates within the same priority, since we want to treat
569 + // those updates as parallel.
570 +
571 + // Unsuspend any update at equal or lower priority.
572 + const higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
573 + root.suspendedLanes &= higherPriorityLanes;
574 + root.pingedLanes &= higherPriorityLanes;
575 +}
576 +
577 +export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
578 + root.suspendedLanes |= suspendedLanes;
579 + root.pingedLanes &= ~suspendedLanes;
580 +}
581 +
582 +export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
583 + root.pingedLanes |= root.suspendedLanes & pingedLanes;
584 +}
585 +
586 +export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
587 + root.expiredLanes |= expiredLanes & root.pendingLanes;
588 +}
589 +
590 +export function markDiscreteUpdatesExpired(root: FiberRoot) {
591 + root.expiredLanes |= InputDiscreteLanes & root.pendingLanes;
592 +}
593 +
594 +export function hasDiscreteLanes(lanes: Lanes) {
595 + return (lanes & InputDiscreteLanes) !== NoLanes;
596 +}
597 +
598 +export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
599 + root.mutableReadLanes |= updateLane & root.pendingLanes;
600 +}
601 +
602 +export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
603 + root.pendingLanes = remainingLanes;
604 +
605 + // Let's try everything again
606 + root.suspendedLanes = 0;
607 + root.pingedLanes = 0;
608 +
609 + root.expiredLanes &= remainingLanes;
610 + root.mutableReadLanes &= remainingLanes;
611 +}
612 +
613 +export function getBumpedLaneForHydration(
614 + root: FiberRoot,
615 + renderLanes: Lanes,
616 +): Lane {
617 + getHighestPriorityLanes(renderLanes);
618 + const highestLanePriority = return_highestLanePriority;
619 +
620 + let lane;
621 + switch (highestLanePriority) {
622 + case SyncLanePriority:
623 + case SyncBatchedLanePriority:
624 + lane = NoLane;
625 + break;
626 + case InputDiscreteHydrationLanePriority:
627 + case InputDiscreteLanePriority:
628 + lane = InputDiscreteHydrationLane;
629 + break;
630 + case InputContinuousHydrationLanePriority:
631 + case InputContinuousLanePriority:
632 + lane = InputContinuousHydrationLane;
633 + break;
634 + case DefaultHydrationLanePriority:
635 + case DefaultLanePriority:
636 + lane = DefaultHydrationLane;
637 + break;
638 + case TransitionShortHydrationLanePriority:
639 + case TransitionShortLanePriority:
640 + lane = TransitionShortHydrationLane;
641 + break;
642 + case TransitionLongHydrationLanePriority:
643 + case TransitionLongLanePriority:
644 + lane = TransitionLongHydrationLane;
645 + break;
646 + case SelectiveHydrationLanePriority:
647 + lane = SelectiveHydrationLane;
648 + break;
649 + case IdleHydrationLanePriority:
650 + case IdleLanePriority:
651 + lane = IdleHydrationLane;
652 + break;
653 + case OffscreenLanePriority:
654 + case NoLanePriority:
655 + lane = NoLane;
656 + break;
657 + default:
658 + invariant(false, 'Invalid lane: %s. This is a bug in React.', lane);
659 + }
660 +
661 + // Check if the lane we chose is suspended. If so, that indicates that we
662 + // already attempted and failed to hydrate at that level. Also check if we're
663 + // already rendering that lane, which is rare but could happen.
664 + if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
665 + // Give up trying to hydrate and fall back to client render.
666 + return NoLane;
667 + }
668 +
669 + return lane;
670 +}
671 +
672 +const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;
673 +
674 +// Taken from:
675 +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
676 +const log = Math.log;
677 +const LN2 = Math.LN2;
678 +function clz32Fallback(x) {
679 + // Let n be ToUint32(x).
680 + // Let p be the number of leading zero bits in
681 + // the 32-bit binary representation of n.
682 + // Return p.
683 + const asUint = x >>> 0;
684 + if (asUint === 0) {
685 + return 32;
686 + }
687 + return (31 - ((log(asUint) / LN2) | 0)) | 0; // the "| 0" acts like math.floor
688 +}
packages/react-reconciler/src/ReactFiberNewContext.new.js
+36 -79
@@ -10,7 +10,7 @@
10 import type {ReactContext} from 'shared/ReactTypes';
11 import type {Fiber, ContextDependency} from './ReactInternalTypes';
12 import type {StackCursor} from './ReactFiberStack.new';
13 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
13 +import type {Lanes} from './ReactFiberLane';
14
15 import {isPrimaryRenderer} from './ReactFiberHostConfig';
16 import {createCursor, push, pop} from './ReactFiberStack.new';
@@ -20,12 +20,17 @@ import {
20 ClassComponent,
21 DehydratedFragment,
22 } from './ReactWorkTags';
23 -import {isSameOrHigherPriority} from './ReactFiberExpirationTime.new';
23 +import {
24 + NoLanes,
25 + isSubsetOfLanes,
26 + includesSomeLane,
27 + mergeLanes,
28 + pickArbitraryLane,
29 +} from './ReactFiberLane';
30
31 import invariant from 'shared/invariant';
32 import is from 'shared/objectIs';
33 import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.new';
28 -import {NoWork} from './ReactFiberExpirationTime.new';
34 import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
35 import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
36
@@ -148,37 +153,22 @@ export function calculateChangedBits<T>(
153
154 export function scheduleWorkOnParentPath(
155 parent: Fiber | null,
151 - renderExpirationTime: ExpirationTimeOpaque,
156 + renderLanes: Lanes,
157 ) {
153 - // Update the child expiration time of all the ancestors, including
154 - // the alternates.
158 + // Update the child lanes of all the ancestors, including the alternates.
159 let node = parent;
160 while (node !== null) {
161 const alternate = node.alternate;
158 - if (
159 - !isSameOrHigherPriority(
160 - node.childExpirationTime_opaque,
161 - renderExpirationTime,
162 - )
163 - ) {
164 - node.childExpirationTime_opaque = renderExpirationTime;
165 - if (
166 - alternate !== null &&
167 - !isSameOrHigherPriority(
168 - alternate.childExpirationTime_opaque,
169 - renderExpirationTime,
170 - )
171 - ) {
172 - alternate.childExpirationTime_opaque = renderExpirationTime;
162 + if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
163 + node.childLanes = mergeLanes(node.childLanes, renderLanes);
164 + if (alternate !== null) {
165 + alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
166 }
167 } else if (
168 alternate !== null &&
176 - !isSameOrHigherPriority(
177 - alternate.childExpirationTime_opaque,
178 - renderExpirationTime,
179 - )
169 + !isSubsetOfLanes(alternate.childLanes, renderLanes)
170 ) {
181 - alternate.childExpirationTime_opaque = renderExpirationTime;
171 + alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
172 } else {
173 // Neither alternate was updated, which means the rest of the
174 // ancestor path already has sufficient priority.
@@ -192,7 +182,7 @@ export function propagateContextChange(
182 workInProgress: Fiber,
183 context: ReactContext<mixed>,
184 changedBits: number,
195 - renderExpirationTime: ExpirationTimeOpaque,
185 + renderLanes: Lanes,
186 ): void {
187 let fiber = workInProgress.child;
188 if (fiber !== null) {
@@ -218,7 +208,11 @@ export function propagateContextChange(
208
209 if (fiber.tag === ClassComponent) {
210 // Schedule a force update on the work-in-progress.
221 - const update = createUpdate(-1, renderExpirationTime, null);
211 + const update = createUpdate(
212 + -1,
213 + pickArbitraryLane(renderLanes),
214 + null,
215 + );
216 update.tag = ForceUpdate;
217 // TODO: Because we don't have a work-in-progress, this will add the
218 // update to the current fiber, too, which means it will persist even if
@@ -226,34 +220,15 @@ export function propagateContextChange(
220 // worth fixing.
221 enqueueUpdate(fiber, update);
222 }
229 -
230 - if (
231 - !isSameOrHigherPriority(
232 - fiber.expirationTime_opaque,
233 - renderExpirationTime,
234 - )
235 - ) {
236 - fiber.expirationTime_opaque = renderExpirationTime;
237 - }
223 + fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
224 const alternate = fiber.alternate;
239 - if (
240 - alternate !== null &&
241 - !isSameOrHigherPriority(
242 - alternate.expirationTime_opaque,
243 - renderExpirationTime,
244 - )
245 - ) {
246 - alternate.expirationTime_opaque = renderExpirationTime;
225 + if (alternate !== null) {
226 + alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
227 }
228 + scheduleWorkOnParentPath(fiber.return, renderLanes);
229
249 - scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
250 -
251 - // Mark the expiration time on the list, too.
252 - if (
253 - !isSameOrHigherPriority(list.expirationTime, renderExpirationTime)
254 - ) {
255 - list.expirationTime = renderExpirationTime;
256 - }
230 + // Mark the updated lanes on the list, too.
231 + list.lanes = mergeLanes(list.lanes, renderLanes);
232
233 // Since we already found a match, we can stop traversing the
234 // dependency list.
@@ -276,29 +251,16 @@ export function propagateContextChange(
251 parentSuspense !== null,
252 'We just came from a parent so we must have had a parent. This is a bug in React.',
253 );
279 - if (
280 - !isSameOrHigherPriority(
281 - parentSuspense.expirationTime_opaque,
282 - renderExpirationTime,
283 - )
284 - ) {
285 - parentSuspense.expirationTime_opaque = renderExpirationTime;
286 - }
254 + parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
255 const alternate = parentSuspense.alternate;
288 - if (
289 - alternate !== null &&
290 - !isSameOrHigherPriority(
291 - alternate.expirationTime_opaque,
292 - renderExpirationTime,
293 - )
294 - ) {
295 - alternate.expirationTime_opaque = renderExpirationTime;
256 + if (alternate !== null) {
257 + alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
258 }
259 // This is intentionally passing this fiber as the parent
260 // because we want to schedule this fiber as having work
299 - // on its children. We'll use the childExpirationTime on
261 + // on its children. We'll use the childLanes on
262 // this fiber to indicate that a context has changed.
301 - scheduleWorkOnParentPath(parentSuspense, renderExpirationTime);
263 + scheduleWorkOnParentPath(parentSuspense, renderLanes);
264 nextFiber = fiber.sibling;
265 } else {
266 // Traverse down.
@@ -334,7 +296,7 @@ export function propagateContextChange(
296
297 export function prepareToReadContext(
298 workInProgress: Fiber,
337 - renderExpirationTime: ExpirationTimeOpaque,
299 + renderLanes: Lanes,
300 ): void {
301 currentlyRenderingFiber = workInProgress;
302 lastContextDependency = null;
@@ -344,12 +306,7 @@ export function prepareToReadContext(
306 if (dependencies !== null) {
307 const firstContext = dependencies.firstContext;
308 if (firstContext !== null) {
347 - if (
348 - isSameOrHigherPriority(
349 - dependencies.expirationTime,
350 - renderExpirationTime,
351 - )
352 - ) {
309 + if (includesSomeLane(dependencies.lanes, renderLanes)) {
310 // Context list has a pending update. Mark that this fiber performed work.
311 markWorkInProgressReceivedUpdate();
312 }
@@ -411,7 +368,7 @@ export function readContext<T>(
368 // This is the first dependency for this component. Create a new list.
369 lastContextDependency = contextItem;
370 currentlyRenderingFiber.dependencies_new = {
414 - expirationTime: NoWork,
371 + lanes: NoLanes,
372 firstContext: contextItem,
373 responders: null,
374 };
packages/react-reconciler/src/ReactFiberOffscreenComponent.js
+3 -3
@@ -8,7 +8,7 @@
8 */
9
10 import type {ReactNodeList} from 'shared/ReactTypes';
11 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 +import type {Lanes} from './ReactFiberLane';
12
13 export type OffscreenProps = {|
14 // TODO: Pick an API before exposing the Offscreen type. I've chosen an enum
@@ -24,8 +24,8 @@ export type OffscreenProps = {|
24 // We use the existence of the state object as an indicator that the component
25 // is hidden.
26 export type OffscreenState = {|
27 - // TODO: This doesn't do anything, yet. It's always NoWork. But eventually it
27 + // TODO: This doesn't do anything, yet. It's always NoLanes. But eventually it
28 // will represent the pending work that must be included in the render in
29 // order to unhide the component.
30 - baseTime: ExpirationTimeOpaque,
30 + baseLanes: Lanes,
31 |};
packages/react-reconciler/src/ReactFiberReconciler.new.js
+38 -42
@@ -19,7 +19,7 @@ import type {
19 import type {RendererInspectionConfig} from './ReactFiberHostConfig';
20 import {FundamentalComponent} from './ReactWorkTags';
21 import type {ReactNodeList} from 'shared/ReactTypes';
22 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
22 +import type {Lane} from './ReactFiberLane';
23 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
24
25 import {
@@ -47,7 +47,7 @@ import {createFiberRoot} from './ReactFiberRoot.new';
47 import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.new';
48 import {
49 requestEventTime,
50 - requestUpdateExpirationTime,
50 + requestUpdateLane,
51 scheduleUpdateOnFiber,
52 flushRoot,
53 batchedEventUpdates,
@@ -74,11 +74,12 @@ import {
74 } from './ReactCurrentFiber';
75 import {StrictMode} from './ReactTypeOfMode';
76 import {
77 - Sync,
78 - ContinuousHydration,
79 - UserBlockingUpdateTime,
80 - isSameOrHigherPriority,
81 -} from './ReactFiberExpirationTime.new';
77 + SyncLane,
78 + InputDiscreteHydrationLane,
79 + SelectiveHydrationLane,
80 + getHighestPriorityPendingLanes,
81 + higherPriorityLane,
82 +} from './ReactFiberLane';
83 import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
84 import {
85 scheduleRefresh,
@@ -235,7 +236,7 @@ export function updateContainer(
236 container: OpaqueRoot,
237 parentComponent: ?React$Component<any, any>,
238 callback: ?Function,
238 -): ExpirationTimeOpaque {
239 +): Lane {
240 if (__DEV__) {
241 onScheduleRoot(container, element);
242 }
@@ -249,7 +250,7 @@ export function updateContainer(
250 }
251 }
252 const suspenseConfig = requestCurrentSuspenseConfig();
252 - const expirationTime = requestUpdateExpirationTime(current, suspenseConfig);
253 + const lane = requestUpdateLane(current, suspenseConfig);
254
255 const context = getContextForSubtree(parentComponent);
256 if (container.context === null) {
@@ -275,7 +276,7 @@ export function updateContainer(
276 }
277 }
278
278 - const update = createUpdate(eventTime, expirationTime, suspenseConfig);
279 + const update = createUpdate(eventTime, lane, suspenseConfig);
280 // Caution: React DevTools currently depends on this property
281 // being called "element".
282 update.payload = {element};
@@ -295,9 +296,9 @@ export function updateContainer(
296 }
297
298 enqueueUpdate(current, update);
298 - scheduleUpdateOnFiber(current, expirationTime);
299 + scheduleUpdateOnFiber(current, lane);
300
300 - return expirationTime;
301 + return lane;
302 }
303
304 export {
@@ -336,40 +337,37 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
337 const root: FiberRoot = fiber.stateNode;
338 if (root.hydrate) {
339 // Flush the first scheduled "update".
339 - flushRoot(root, root.firstPendingTime_opaque);
340 + const lanes = getHighestPriorityPendingLanes(root);
341 + flushRoot(root, lanes);
342 }
343 break;
344 case SuspenseComponent:
343 - flushSync(() =>
344 - scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque)),
345 - );
345 + flushSync(() => scheduleUpdateOnFiber(fiber, SyncLane));
346 // If we're still blocked after this, we need to increase
347 // the priority of any promises resolving within this
348 // boundary so that they next attempt also has higher pri.
349 - const retryExpTime = UserBlockingUpdateTime;
350 - markRetryTimeIfNotHydrated(fiber, retryExpTime);
349 + const retryLane = InputDiscreteHydrationLane;
350 + markRetryLaneIfNotHydrated(fiber, retryLane);
351 break;
352 }
353 }
354
355 -function markRetryTimeImpl(fiber: Fiber, retryTime: ExpirationTimeOpaque) {
355 +function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
356 const suspenseState: null | SuspenseState = fiber.memoizedState;
357 if (suspenseState !== null && suspenseState.dehydrated !== null) {
358 - if (!isSameOrHigherPriority(suspenseState.retryTime, retryTime)) {
359 - suspenseState.retryTime = retryTime;
360 - }
358 + suspenseState.retryLane = higherPriorityLane(
359 + suspenseState.retryLane,
360 + retryLane,
361 + );
362 }
363 }
364
365 // Increases the priority of thennables when they resolve within this boundary.
365 -function markRetryTimeIfNotHydrated(
366 - fiber: Fiber,
367 - retryTime: ExpirationTimeOpaque,
368 -) {
369 - markRetryTimeImpl(fiber, retryTime);
366 +function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
367 + markRetryLaneImpl(fiber, retryLane);
368 const alternate = fiber.alternate;
369 if (alternate) {
372 - markRetryTimeImpl(alternate, retryTime);
370 + markRetryLaneImpl(alternate, retryLane);
371 }
372 }
373
@@ -381,9 +379,9 @@ export function attemptUserBlockingHydration(fiber: Fiber): void {
379 // Suspense.
380 return;
381 }
384 - const expTime = UserBlockingUpdateTime;
385 - scheduleUpdateOnFiber(fiber, expTime);
386 - markRetryTimeIfNotHydrated(fiber, expTime);
382 + const lane = InputDiscreteHydrationLane;
383 + scheduleUpdateOnFiber(fiber, lane);
384 + markRetryLaneIfNotHydrated(fiber, lane);
385 }
386
387 export function attemptContinuousHydration(fiber: Fiber): void {
@@ -394,11 +392,9 @@ export function attemptContinuousHydration(fiber: Fiber): void {
392 // Suspense.
393 return;
394 }
397 - scheduleUpdateOnFiber(fiber, (ContinuousHydration: ExpirationTimeOpaque));
398 - markRetryTimeIfNotHydrated(
399 - fiber,
400 - (ContinuousHydration: ExpirationTimeOpaque),
401 - );
395 + const lane = SelectiveHydrationLane;
396 + scheduleUpdateOnFiber(fiber, lane);
397 + markRetryLaneIfNotHydrated(fiber, lane);
398 }
399
400 export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
@@ -407,9 +403,9 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
403 // their priority other than synchronously flush it.
404 return;
405 }
410 - const expTime = requestUpdateExpirationTime(fiber, null);
411 - scheduleUpdateOnFiber(fiber, expTime);
412 - markRetryTimeIfNotHydrated(fiber, expTime);
406 + const lane = requestUpdateLane(fiber, null);
407 + scheduleUpdateOnFiber(fiber, lane);
408 + markRetryLaneIfNotHydrated(fiber, lane);
409 }
410
411 export {findHostInstance};
@@ -491,7 +487,7 @@ if (__DEV__) {
487 // Shallow cloning props works as a workaround for now to bypass the bailout check.
488 fiber.memoizedProps = {...fiber.memoizedProps};
489
494 - scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
490 + scheduleUpdateOnFiber(fiber, SyncLane);
491 }
492 };
493
@@ -501,11 +497,11 @@ if (__DEV__) {
497 if (fiber.alternate) {
498 fiber.alternate.pendingProps = fiber.pendingProps;
499 }
504 - scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
500 + scheduleUpdateOnFiber(fiber, SyncLane);
501 };
502
503 scheduleUpdate = (fiber: Fiber) => {
508 - scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
504 + scheduleUpdateOnFiber(fiber, SyncLane);
505 };
506
507 setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
packages/react-reconciler/src/ReactFiberRoot.new.js
+10 -170
@@ -8,25 +8,17 @@
8 */
9
10 import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes';
11 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 import type {RootTag} from './ReactRootTags';
12
13 import {noTimeout} from './ReactFiberHostConfig';
14 import {createHostRootFiber} from './ReactFiber.new';
16 -import {
17 - NoWork,
18 - isSameOrHigherPriority,
19 - isSameExpirationTime,
20 - bumpPriorityHigher,
21 - bumpPriorityLower,
22 -} from './ReactFiberExpirationTime.new';
15 +import {NoLanes} from './ReactFiberLane';
16 import {
17 enableSchedulerTracing,
18 enableSuspenseCallback,
19 } from 'shared/ReactFeatureFlags';
20 import {unstable_getThreadID} from 'scheduler/tracing';
21 import {initializeUpdateQueue} from './ReactUpdateQueue.new';
29 -import {clearPendingUpdates as clearPendingMutableSourceUpdates} from './ReactMutableSource.new';
22
23 function FiberRootNode(containerInfo, tag, hydrate) {
24 this.tag = tag;
@@ -35,23 +27,22 @@ function FiberRootNode(containerInfo, tag, hydrate) {
27 this.current = null;
28 this.pingCache = null;
29 this.finishedWork = null;
38 - this.finishedExpirationTime_opaque = NoWork;
30 this.timeoutHandle = noTimeout;
31 this.context = null;
32 this.pendingContext = null;
33 this.hydrate = hydrate;
34 this.callbackNode = null;
44 - this.callbackId = NoWork;
35 + this.callbackId = NoLanes;
36 this.callbackIsSync = false;
37 this.expiresAt = -1;
47 - this.firstPendingTime_opaque = NoWork;
48 - this.lastPendingTime_opaque = NoWork;
49 - this.firstSuspendedTime_opaque = NoWork;
50 - this.lastSuspendedTime_opaque = NoWork;
51 - this.nextKnownPendingLevel_opaque = NoWork;
52 - this.lastPingedTime_opaque = NoWork;
53 - this.lastExpiredTime_opaque = NoWork;
54 - this.mutableSourceLastPendingUpdateTime_opaque = NoWork;
38 +
39 + this.pendingLanes = NoLanes;
40 + this.suspendedLanes = NoLanes;
41 + this.pingedLanes = NoLanes;
42 + this.expiredLanes = NoLanes;
43 + this.mutableReadLanes = NoLanes;
44 +
45 + this.finishedLanes = NoLanes;
46
47 if (enableSchedulerTracing) {
48 this.interactionThreadID = unstable_getThreadID();
@@ -84,154 +75,3 @@ export function createFiberRoot(
75
76 return root;
77 }
87 -
88 -export function isRootSuspendedAtTime(
89 - root: FiberRoot,
90 - expirationTime: ExpirationTimeOpaque,
91 -): boolean {
92 - const firstSuspendedTime = root.firstSuspendedTime_opaque;
93 - const lastSuspendedTime = root.lastSuspendedTime_opaque;
94 - return (
95 - !isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque)) &&
96 - isSameOrHigherPriority(firstSuspendedTime, expirationTime) &&
97 - isSameOrHigherPriority(expirationTime, lastSuspendedTime)
98 - );
99 -}
100 -
101 -export function markRootSuspendedAtTime(
102 - root: FiberRoot,
103 - expirationTime: ExpirationTimeOpaque,
104 -): void {
105 - const firstSuspendedTime = root.firstSuspendedTime_opaque;
106 - const lastSuspendedTime = root.lastSuspendedTime_opaque;
107 - if (!isSameOrHigherPriority(firstSuspendedTime, expirationTime)) {
108 - root.firstSuspendedTime_opaque = expirationTime;
109 - }
110 - if (
111 - !isSameOrHigherPriority(expirationTime, lastSuspendedTime) ||
112 - isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque))
113 - ) {
114 - root.lastSuspendedTime_opaque = expirationTime;
115 - }
116 -
117 - if (isSameOrHigherPriority(root.lastPingedTime_opaque, expirationTime)) {
118 - root.lastPingedTime_opaque = NoWork;
119 - }
120 -
121 - if (isSameOrHigherPriority(root.lastExpiredTime_opaque, expirationTime)) {
122 - root.lastExpiredTime_opaque = NoWork;
123 - }
124 -}
125 -
126 -export function markRootUpdatedAtTime(
127 - root: FiberRoot,
128 - expirationTime: ExpirationTimeOpaque,
129 -): void {
130 - // Update the range of pending times
131 - const firstPendingTime = root.firstPendingTime_opaque;
132 - if (!isSameOrHigherPriority(firstPendingTime, expirationTime)) {
133 - root.firstPendingTime_opaque = expirationTime;
134 - }
135 - const lastPendingTime = root.lastPendingTime_opaque;
136 - if (
137 - isSameExpirationTime(lastPendingTime, (NoWork: ExpirationTimeOpaque)) ||
138 - !isSameOrHigherPriority(expirationTime, lastPendingTime)
139 - ) {
140 - root.lastPendingTime_opaque = expirationTime;
141 - }
142 -
143 - // Update the range of suspended times. Treat everything lower priority or
144 - // equal to this update as unsuspended.
145 - const firstSuspendedTime = root.firstSuspendedTime_opaque;
146 - if (
147 - !isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque))
148 - ) {
149 - if (isSameOrHigherPriority(expirationTime, firstSuspendedTime)) {
150 - // The entire suspended range is now unsuspended.
151 - root.firstSuspendedTime_opaque = root.lastSuspendedTime_opaque = root.nextKnownPendingLevel_opaque = NoWork;
152 - } else if (
153 - isSameOrHigherPriority(expirationTime, root.lastSuspendedTime_opaque)
154 - ) {
155 - root.lastSuspendedTime_opaque = bumpPriorityHigher(expirationTime);
156 - }
157 -
158 - // This is a pending level. Check if it's higher priority than the next
159 - // known pending level.
160 - if (
161 - !isSameOrHigherPriority(root.nextKnownPendingLevel_opaque, expirationTime)
162 - ) {
163 - root.nextKnownPendingLevel_opaque = expirationTime;
164 - }
165 - }
166 -}
167 -
168 -export function markRootFinishedAtTime(
169 - root: FiberRoot,
170 - finishedExpirationTime: ExpirationTimeOpaque,
171 - remainingExpirationTime: ExpirationTimeOpaque,
172 -): void {
173 - // Update the range of pending times
174 - root.firstPendingTime_opaque = remainingExpirationTime;
175 - if (
176 - !isSameOrHigherPriority(
177 - remainingExpirationTime,
178 - root.lastPendingTime_opaque,
179 - )
180 - ) {
181 - // This usually means we've finished all the work, but it can also happen
182 - // when something gets downprioritized during render, like a hidden tree.
183 - root.lastPendingTime_opaque = remainingExpirationTime;
184 - }
185 -
186 - // Update the range of suspended times. Treat everything higher priority or
187 - // equal to this update as unsuspended.
188 - if (
189 - isSameOrHigherPriority(
190 - root.lastSuspendedTime_opaque,
191 - finishedExpirationTime,
192 - )
193 - ) {
194 - // The entire suspended range is now unsuspended.
195 - root.firstSuspendedTime_opaque = root.lastSuspendedTime_opaque = root.nextKnownPendingLevel_opaque = NoWork;
196 - } else if (
197 - isSameOrHigherPriority(
198 - root.firstSuspendedTime_opaque,
199 - finishedExpirationTime,
200 - )
201 - ) {
202 - // Part of the suspended range is now unsuspended. Narrow the range to
203 - // include everything between the unsuspended time (non-inclusive) and the
204 - // last suspended time.
205 - root.firstSuspendedTime_opaque = bumpPriorityLower(finishedExpirationTime);
206 - }
207 -
208 - if (
209 - isSameOrHigherPriority(root.lastPingedTime_opaque, finishedExpirationTime)
210 - ) {
211 - // Clear the pinged time
212 - root.lastPingedTime_opaque = NoWork;
213 - }
214 -
215 - if (
216 - isSameOrHigherPriority(root.lastExpiredTime_opaque, finishedExpirationTime)
217 - ) {
218 - // Clear the expired time
219 - root.lastExpiredTime_opaque = NoWork;
220 - }
221 -
222 - // Clear any pending updates that were just processed.
223 - clearPendingMutableSourceUpdates(root, finishedExpirationTime);
224 -}
225 -
226 -export function markRootExpiredAtTime(
227 - root: FiberRoot,
228 - expirationTime: ExpirationTimeOpaque,
229 -): void {
230 - const lastExpiredTime = root.lastExpiredTime_opaque;
231 - if (
232 - isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque)) ||
233 - !isSameOrHigherPriority(expirationTime, lastExpiredTime)
234 - ) {
235 - root.lastExpiredTime_opaque = expirationTime;
236 - }
237 -}
packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
+5 -6
@@ -9,7 +9,7 @@
9
10 import type {Fiber} from './ReactInternalTypes';
11 import type {SuspenseInstance} from './ReactFiberHostConfig';
12 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
12 +import type {Lane} from './ReactFiberLane';
13 import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
14 import {NoEffect, DidCapture} from './ReactSideEffectTags';
15 import {
@@ -29,11 +29,10 @@ export type SuspenseState = {|
29 // here to indicate that it is dehydrated (flag) and for quick access
30 // to check things like isSuspenseInstancePending.
31 dehydrated: null | SuspenseInstance,
32 - // Represents the earliest expiration time we should attempt to hydrate
33 - // a dehydrated boundary at.
34 - // Never is the default for dehydrated boundaries.
35 - // NoWork is the default for normal boundaries, which turns into "normal" pri.
36 - retryTime: ExpirationTimeOpaque,
32 + // Represents the lane we should attempt to hydrate a dehydrated boundary at.
33 + // OffscreenLane is the default for dehydrated boundaries.
34 + // NoLane is the default for normal boundaries, which turns into "normal" pri.
35 + retryLane: Lane,
36 |};
37
38 export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
packages/react-reconciler/src/ReactFiberThrow.new.js
+32 -46
@@ -9,7 +9,7 @@
9
10 import type {Fiber} from './ReactInternalTypes';
11 import type {FiberRoot} from './ReactInternalTypes';
12 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
12 +import type {Lane, Lanes} from './ReactFiberLane';
13 import type {CapturedValue} from './ReactCapturedValue';
14 import type {Update} from './ReactUpdateQueue.new';
15 import type {Wakeable} from 'shared/ReactTypes';
@@ -54,16 +54,21 @@ import {
54 } from './ReactFiberWorkLoop.new';
55 import {logCapturedError} from './ReactFiberErrorLogger';
56
57 -import {Sync, isSameExpirationTime} from './ReactFiberExpirationTime.new';
57 +import {
58 + SyncLane,
59 + includesSomeLane,
60 + mergeLanes,
61 + pickArbitraryLane,
62 +} from './ReactFiberLane';
63
64 const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
65
66 function createRootErrorUpdate(
67 fiber: Fiber,
68 errorInfo: CapturedValue<mixed>,
64 - expirationTime: ExpirationTimeOpaque,
69 + lane: Lane,
70 ): Update<mixed> {
66 - const update = createUpdate(-1, expirationTime, null);
71 + const update = createUpdate(-1, lane, null);
72 // Unmount the root by rendering null.
73 update.tag = CaptureUpdate;
74 // Caution: React DevTools currently depends on this property
@@ -80,9 +85,9 @@ function createRootErrorUpdate(
85 function createClassErrorUpdate(
86 fiber: Fiber,
87 errorInfo: CapturedValue<mixed>,
83 - expirationTime: ExpirationTimeOpaque,
88 + lane: Lane,
89 ): Update<mixed> {
85 - const update = createUpdate(-1, expirationTime, null);
90 + const update = createUpdate(-1, lane, null);
91 update.tag = CaptureUpdate;
92 const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
93 if (typeof getDerivedStateFromError === 'function') {
@@ -120,12 +125,7 @@ function createClassErrorUpdate(
125 // If componentDidCatch is the only error boundary method defined,
126 // then it needs to call setState to recover from errors.
127 // If no state update is scheduled then the boundary will swallow the error.
123 - if (
124 - !isSameExpirationTime(
125 - fiber.expirationTime_opaque,
126 - (Sync: ExpirationTimeOpaque),
127 - )
128 - ) {
128 + if (!includesSomeLane(fiber.lanes, (SyncLane: Lane))) {
129 console.error(
130 '%s: Error boundaries should implement getDerivedStateFromError(). ' +
131 'In that method, return a state update to display an error message or fallback UI.',
@@ -143,14 +143,10 @@ function createClassErrorUpdate(
143 return update;
144 }
145
146 -function attachPingListener(
147 - root: FiberRoot,
148 - renderExpirationTime: ExpirationTimeOpaque,
149 - wakeable: Wakeable,
150 -) {
151 - // Attach a listener to the promise to "ping" the root and retry. But
152 - // only if one does not already exist for the current render expiration
153 - // time (which acts like a "thread ID" here).
146 +function attachPingListener(root: FiberRoot, wakeable: Wakeable, lanes: Lanes) {
147 + // Attach a listener to the promise to "ping" the root and retry. But only if
148 + // one does not already exist for the lanes we're currently rendering (which
149 + // acts like a "thread ID" here).
150 let pingCache = root.pingCache;
151 let threadIDs;
152 if (pingCache === null) {
@@ -164,15 +160,10 @@ function attachPingListener(
160 pingCache.set(wakeable, threadIDs);
161 }
162 }
167 - if (!threadIDs.has(renderExpirationTime)) {
163 + if (!threadIDs.has(lanes)) {
164 // Memoize using the thread ID to prevent redundant listeners.
169 - threadIDs.add(renderExpirationTime);
170 - const ping = pingSuspendedRoot.bind(
171 - null,
172 - root,
173 - wakeable,
174 - renderExpirationTime,
175 - );
165 + threadIDs.add(lanes);
166 + const ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
167 wakeable.then(ping, ping);
168 }
169 }
@@ -182,7 +173,7 @@ function throwException(
173 returnFiber: Fiber,
174 sourceFiber: Fiber,
175 value: mixed,
185 - renderExpirationTime: ExpirationTimeOpaque,
176 + rootRenderLanes: Lanes,
177 ) {
178 // The source fiber did not complete.
179 sourceFiber.effectTag |= Incomplete;
@@ -204,7 +195,7 @@ function throwException(
195 if (currentSource) {
196 sourceFiber.updateQueue = currentSource.updateQueue;
197 sourceFiber.memoizedState = currentSource.memoizedState;
207 - sourceFiber.expirationTime_opaque = currentSource.expirationTime_opaque;
198 + sourceFiber.lanes = currentSource.lanes;
199 } else {
200 sourceFiber.updateQueue = null;
201 sourceFiber.memoizedState = null;
@@ -263,11 +254,7 @@ function throwException(
254 // When we try rendering again, we should not reuse the current fiber,
255 // since it's known to be in an inconsistent state. Use a force update to
256 // prevent a bail out.
266 - const update = createUpdate(
267 - -1,
268 - (Sync: ExpirationTimeOpaque),
269 - null,
270 - );
257 + const update = createUpdate(-1, SyncLane, null);
258 update.tag = ForceUpdate;
259 enqueueUpdate(sourceFiber, update);
260 }
@@ -275,7 +262,7 @@ function throwException(
262
263 // The source fiber did not complete. Mark it with Sync priority to
264 // indicate that it still has pending work.
278 - sourceFiber.expirationTime_opaque = Sync;
265 + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane);
266
267 // Exit without suspending.
268 return;
@@ -323,10 +310,10 @@ function throwException(
310 // We want to ensure that a "busy" state doesn't get force committed. We want to
311 // ensure that new initial loading states can commit as soon as possible.
312
326 - attachPingListener(root, renderExpirationTime, wakeable);
313 + attachPingListener(root, wakeable, rootRenderLanes);
314
315 workInProgress.effectTag |= ShouldCapture;
329 - workInProgress.expirationTime_opaque = renderExpirationTime;
316 + workInProgress.lanes = rootRenderLanes;
317
318 return;
319 }
@@ -349,6 +336,7 @@ function throwException(
336 // over and traverse parent path again, this time treating the exception
337 // as an error.
338 renderDidError();
339 +
340 value = createCapturedValue(value, sourceFiber);
341 let workInProgress = returnFiber;
342 do {
@@ -356,12 +344,9 @@ function throwException(
344 case HostRoot: {
345 const errorInfo = value;
346 workInProgress.effectTag |= ShouldCapture;
359 - workInProgress.expirationTime_opaque = renderExpirationTime;
360 - const update = createRootErrorUpdate(
361 - workInProgress,
362 - errorInfo,
363 - renderExpirationTime,
364 - );
347 + const lane = pickArbitraryLane(rootRenderLanes);
348 + workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
349 + const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
350 enqueueCapturedUpdate(workInProgress, update);
351 return;
352 }
@@ -378,12 +363,13 @@ function throwException(
363 !isAlreadyFailedLegacyErrorBoundary(instance)))
364 ) {
365 workInProgress.effectTag |= ShouldCapture;
381 - workInProgress.expirationTime_opaque = renderExpirationTime;
366 + const lane = pickArbitraryLane(rootRenderLanes);
367 + workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
368 // Schedule the error boundary to re-render using updated state
369 const update = createClassErrorUpdate(
370 workInProgress,
371 errorInfo,
386 - renderExpirationTime,
372 + lane,
373 );
374 enqueueCapturedUpdate(workInProgress, update);
375 return;
packages/react-reconciler/src/ReactFiberUnwindWork.new.js
+5 -8
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 +import type {Lanes} from './ReactFiberLane';
12 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
13
14 import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
@@ -35,14 +35,11 @@ import {
35 popTopLevelContextObject as popTopLevelLegacyContextObject,
36 } from './ReactFiberContext.new';
37 import {popProvider} from './ReactFiberNewContext.new';
38 -import {popRenderExpirationTime} from './ReactFiberWorkLoop.new';
38 +import {popRenderLanes} from './ReactFiberWorkLoop.new';
39
40 import invariant from 'shared/invariant';
41
42 -function unwindWork(
43 - workInProgress: Fiber,
44 - renderExpirationTime: ExpirationTimeOpaque,
45 -) {
42 +function unwindWork(workInProgress: Fiber, renderLanes: Lanes) {
43 switch (workInProgress.tag) {
44 case ClassComponent: {
45 const Component = workInProgress.type;
@@ -110,7 +107,7 @@ function unwindWork(
107 return null;
108 case OffscreenComponent:
109 case LegacyHiddenComponent:
113 - popRenderExpirationTime(workInProgress);
110 + popRenderLanes(workInProgress);
111 return null;
112 default:
113 return null;
@@ -150,7 +147,7 @@ function unwindInterruptedWork(interruptedWork: Fiber) {
147 break;
148 case OffscreenComponent:
149 case LegacyHiddenComponent:
153 - popRenderExpirationTime(interruptedWork);
150 + popRenderLanes(interruptedWork);
151 break;
152 default:
153 break;
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+454 -733
@@ -9,7 +9,7 @@
9
10 import type {Thenable, Wakeable} from 'shared/ReactTypes';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
12 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
12 +import type {Lanes, Lane} from './ReactFiberLane';
13 import type {ReactPriorityLevel} from './ReactInternalTypes';
14 import type {Interaction} from 'scheduler/src/Tracing';
15 import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
@@ -43,8 +43,6 @@ import {
43 ImmediatePriority as ImmediateSchedulerPriority,
44 UserBlockingPriority as UserBlockingSchedulerPriority,
45 NormalPriority as NormalSchedulerPriority,
46 - LowPriority as LowSchedulerPriority,
47 - IdlePriority as IdleSchedulerPriority,
46 flushSyncCallbackQueue,
47 scheduleSyncCallback,
48 } from './SchedulerWithReactIntegration.new';
@@ -69,13 +67,6 @@ import {
67 createWorkInProgress,
68 assignFiberPropertiesInDEV,
69 } from './ReactFiber.new';
72 -import {
73 - isRootSuspendedAtTime,
74 - markRootSuspendedAtTime,
75 - markRootFinishedAtTime,
76 - markRootUpdatedAtTime,
77 - markRootExpiredAtTime,
78 -} from './ReactFiberRoot.new';
70 import {
71 NoMode,
72 StrictMode,
@@ -93,6 +84,8 @@ import {
84 MemoComponent,
85 SimpleMemoComponent,
86 Block,
87 + OffscreenComponent,
88 + LegacyHiddenComponent,
89 } from './ReactWorkTags';
90 import {LegacyRoot} from './ReactRootTags';
91 import {
@@ -113,33 +106,35 @@ import {
106 Hydrating,
107 HydratingAndUpdate,
108 } from './ReactSideEffectTags';
116 -import {
117 - NoWork,
118 - Sync,
119 - UserBlockingUpdateTime,
120 - DefaultUpdateTime,
121 - Never,
122 - inferPriorityFromExpirationTime,
123 - Batched,
124 - Idle,
125 - ContinuousHydration,
126 - ShortTransition,
127 - LongTransition,
128 - isSameOrHigherPriority,
129 - isSameExpirationTime,
130 - bumpPriorityLower,
131 -} from './ReactFiberExpirationTime.new';
109 import {
110 SyncLanePriority,
134 - SyncBatchedLanePriority,
111 InputDiscreteLanePriority,
136 - InputContinuousLanePriority,
137 - DefaultLanePriority,
112 TransitionShortLanePriority,
113 TransitionLongLanePriority,
140 - HydrationContinuousLanePriority,
141 - IdleLanePriority,
142 - OffscreenLanePriority,
114 + NoLanes,
115 + NoLane,
116 + SyncLane,
117 + SyncBatchedLane,
118 + OffscreenLane,
119 + findUpdateLane,
120 + findTransitionLane,
121 + includesSomeLane,
122 + isSubsetOfLanes,
123 + mergeLanes,
124 + removeLanes,
125 + hasDiscreteLanes,
126 + hasUpdatePriority,
127 + getNextLanes,
128 + returnNextLanesPriority,
129 + getLanesToRetrySynchronouslyOnError,
130 + markRootUpdated,
131 + markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
132 + markRootPinged,
133 + markRootExpired,
134 + markDiscreteUpdatesExpired,
135 + markRootFinished,
136 + schedulerPriorityToLanePriority,
137 + lanePriorityToSchedulerPriority,
138 } from './ReactFiberLane';
139 import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
140 import {completeWork} from './ReactFiberCompleteWork.new';
@@ -235,33 +230,43 @@ let executionContext: ExecutionContext = NoContext;
230 let workInProgressRoot: FiberRoot | null = null;
231 // The fiber we're working on
232 let workInProgress: Fiber | null = null;
238 -// The expiration time we're rendering
239 -let renderExpirationTime: ExpirationTimeOpaque = NoWork;
240 -
241 -// Stack that allows components to channge renderExpirationTime for its subtree
242 -const renderExpirationTimeCursor: StackCursor<ExpirationTimeOpaque> = createCursor(
243 - NoWork,
244 -);
233 +// The lanes we're rendering
234 +let workInProgressRootRenderLanes: Lanes = NoLanes;
235 +
236 +// Stack that allows components to change the render lanes for its subtree
237 +// This is a superset of the lanes we started working on at the root. The only
238 +// case where it's different from `workInProgressRootRenderLanes` is when we
239 +// enter a subtree that is hidden and needs to be unhidden: Suspense and
240 +// Offscreen component.
241 +//
242 +// Most things in the work loop should deal with workInProgressRootRenderLanes.
243 +// Most things in begin/complete phases should deal with subtreeRenderLanes.
244 +let subtreeRenderLanes: Lanes = NoLanes;
245 +const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
246
247 // Whether to root completed, errored, suspended, etc.
248 let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
249 // A fatal error, if one is thrown
250 let workInProgressRootFatalError: mixed = null;
251 // Most recent event time among processed updates during this render.
251 -// This is conceptually a time stamp but expressed in terms of an ExpirationTimeOpaque
252 -// because we deal mostly with expiration times in the hot path, so this avoids
253 -// the conversion happening in the hot path.
252 let workInProgressRootLatestProcessedEventTime: number = -1;
253 let workInProgressRootLatestSuspenseTimeout: number = -1;
254 let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
255 +// "Included" lanes refer to lanes that were worked on during this render. It's
256 +// slightly different than `renderLanes` because `renderLanes` can change as you
257 +// enter and exit an Offscreen tree. This value is the combination of all render
258 +// lanes for the entire render phase.
259 +let workInProgressRootIncludedLanes: Lanes = NoLanes;
260 // The work left over by components that were visited during this render. Only
261 // includes unprocessed updates, not work in bailed out children.
259 -let workInProgressRootNextUnprocessedUpdateTime: ExpirationTimeOpaque = NoWork;
262 +let workInProgressRootSkippedLanes: Lanes = NoLanes;
263 +// Lanes that were updated (in an interleaved event) during this render.
264 +let workInProgressRootUpdatedLanes: Lanes = NoLanes;
265 +// Lanes that were pinged (in an interleaved event) during this render.
266 +let workInProgressRootPingedLanes: Lanes = NoLanes;
267 +
268 +let mostRecentlyUpdatedRoot: FiberRoot | null = null;
269
261 -// If we're pinged while rendering we don't always restart immediately.
262 -// This flag determines if it might be worthwhile to restart if an opportunity
263 -// happens latere.
264 -let workInProgressRootHasPendingPing: boolean = false;
270 // The most recent time we committed a fallback. This lets us ensure a train
271 // model where we don't commit new loading states in too quick succession.
272 let globalMostRecentFallbackTime: number = 0;
@@ -276,15 +281,12 @@ let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
281 let rootDoesHavePassiveEffects: boolean = false;
282 let rootWithPendingPassiveEffects: FiberRoot | null = null;
283 let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
279 -let pendingPassiveEffectsExpirationTime: ExpirationTimeOpaque = NoWork;
284 +let pendingPassiveEffectsLanes: Lanes = NoLanes;
285 let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
286 let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
287 let pendingPassiveProfilerEffects: Array<Fiber> = [];
288
284 -let rootsWithPendingDiscreteUpdates: Map<
285 - FiberRoot,
286 - ExpirationTimeOpaque,
287 -> | null = null;
289 +let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
290
291 // Use these to prevent an infinite loop of nested updates
292 const NESTED_UPDATE_LIMIT = 50;
@@ -294,16 +296,19 @@ let rootWithNestedUpdates: FiberRoot | null = null;
296 const NESTED_PASSIVE_UPDATE_LIMIT = 50;
297 let nestedPassiveUpdateCount: number = 0;
298
297 -// Marks the need to reschedule pending interactions at these expiration times
299 +// Marks the need to reschedule pending interactions at these lanes
300 // during the commit phase. This enables them to be traced across components
301 // that spawn new work during render. E.g. hidden boundaries, suspended SSR
302 // hydration or SuspenseList.
301 -let spawnedWorkDuringRender: null | Array<ExpirationTimeOpaque> = null;
303 +// TODO: Can use a bitmask instead of an array
304 +let spawnedWorkDuringRender: null | Array<Lane | Lanes> = null;
305
306 // If two updates are scheduled within the same event, we should treat their
307 // event times as simultaneous, even if the actual clock time has advanced
308 // between the first and second call.
309 let currentEventTime: number = -1;
310 +let currentEventWipLanes: Lanes = NoLanes;
311 +let currentEventPendingLanes: Lanes = NoLanes;
312
313 // Dev only flag that tracks if passive effects are currently being flushed.
314 // We warn about state updates for unmounted components differently in this case.
@@ -335,121 +340,86 @@ export function getCurrentTime() {
340 return now();
341 }
342
338 -export function requestUpdateExpirationTime(
343 +export function requestUpdateLane(
344 fiber: Fiber,
345 suspenseConfig: SuspenseConfig | null,
341 -): ExpirationTimeOpaque {
346 +): Lane {
347 // Special cases
348 const mode = fiber.mode;
349 if ((mode & BlockingMode) === NoMode) {
345 - return Sync;
350 + return (SyncLane: Lane);
351 } else if ((mode & ConcurrentMode) === NoMode) {
352 return getCurrentPriorityLevel() === ImmediateSchedulerPriority
348 - ? (Sync: ExpirationTimeOpaque)
349 - : Batched;
350 - } else if ((executionContext & RenderContext) !== NoContext) {
351 - // Use whatever time we're already rendering
352 - // TODO: Treat render phase updates as if they came from an
353 - // interleaved event.
354 - return renderExpirationTime;
355 - }
356 -
357 - let updateLanePriority;
353 + ? (SyncLane: Lane)
354 + : (SyncBatchedLane: Lane);
355 + }
356 +
357 + // The algorithm for assigning an update to a lane should be stable for all
358 + // updates at the same priority within the same event. To do this, the inputs
359 + // to the algorithm must be the same. For example, we use the `renderLanes`
360 + // to avoid choosing a lane that is already in the middle of rendering.
361 + //
362 + // However, the "included" lanes could be mutated in between updates in the
363 + // same event, like if you perform an update inside `flushSync`. Or any other
364 + // codepath that might call `prepareFreshStack`.
365 + //
366 + // The trick we use is to cache the first of each of these inputs within an
367 + // event. Then reset the cached values once we can be sure the event is over.
368 + // Our heuristic for that is whenever we enter a concurrent work loop.
369 + //
370 + // We'll do the same for `currentEventPendingLanes` below.
371 + if (currentEventWipLanes === NoLanes) {
372 + currentEventWipLanes = workInProgressRootIncludedLanes;
373 + }
374 +
375 + let lane;
376 if (suspenseConfig !== null) {
359 - // If there's a SuspenseConfig, choose an expiration time that's lower
360 - // priority than a normal concurrent update (regardless of the current
361 - // Scheduler priority.) Timeouts larger than 10 seconds move one level
362 - // lower than that.
377 + // Use the size of the timeout as a heuristic to prioritize shorter
378 + // transitions over longer ones.
379 // TODO: This will coerce numbers larger than 31 bits to 0.
380 const timeoutMs = suspenseConfig.timeoutMs;
365 - updateLanePriority =
381 + const transitionLanePriority =
382 timeoutMs === undefined || (timeoutMs | 0) < 10000
383 ? TransitionShortLanePriority
384 : TransitionLongLanePriority;
385 +
386 + if (currentEventPendingLanes !== NoLanes) {
387 + currentEventPendingLanes =
388 + mostRecentlyUpdatedRoot !== null
389 + ? mostRecentlyUpdatedRoot.pendingLanes
390 + : NoLanes;
391 + }
392 +
393 + lane = findTransitionLane(
394 + transitionLanePriority,
395 + currentEventWipLanes,
396 + currentEventPendingLanes,
397 + );
398 } else {
399 // TODO: If we're not inside `runWithPriority`, this returns the priority
400 // of the currently running task. That's probably not what we want.
372 - const priorityLevel = getCurrentPriorityLevel();
373 - switch (priorityLevel) {
374 - case ImmediateSchedulerPriority:
375 - updateLanePriority = SyncLanePriority;
376 - break;
377 - case UserBlockingSchedulerPriority:
378 - updateLanePriority = InputContinuousLanePriority;
379 - break;
380 - case NormalSchedulerPriority:
381 - case LowSchedulerPriority:
382 - // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
383 - updateLanePriority = DefaultLanePriority;
384 - break;
385 - case IdleSchedulerPriority:
386 - updateLanePriority = IdleLanePriority;
387 - break;
388 - default:
389 - invariant(false, 'Expected a valid priority level');
390 - }
391 - }
392 -
393 - // TODO: In the new system, what we'll do here is claim one of the bits of
394 - // the root's `pendingLanes` field, based on its priority. We'll combine this
395 - // function with `scheduleUpdateOnFiber` and `markRootUpdatedAtTime`.
396 - let expirationTime;
397 - switch (updateLanePriority) {
398 - case SyncLanePriority:
399 - return Sync;
400 - case SyncBatchedLanePriority:
401 - return Batched;
402 - case InputDiscreteLanePriority:
403 - case InputContinuousLanePriority:
404 - expirationTime = UserBlockingUpdateTime;
405 - break;
406 - case DefaultLanePriority:
407 - expirationTime = DefaultUpdateTime;
408 - break;
409 - case TransitionShortLanePriority:
410 - expirationTime = ShortTransition;
411 - break;
412 - case TransitionLongLanePriority:
413 - expirationTime = LongTransition;
414 - break;
415 - case HydrationContinuousLanePriority:
416 - expirationTime = ContinuousHydration;
417 - break;
418 - case IdleLanePriority:
419 - expirationTime = Idle;
420 - break;
421 - case OffscreenLanePriority:
422 - expirationTime = Never;
423 - break;
424 - default:
425 - invariant(false, 'Expected a valid priority level');
426 - }
401 + const schedulerPriority = getCurrentPriorityLevel();
402
428 - // If we're in the middle of rendering a tree, do not update at the same
429 - // expiration time that is already rendering.
430 - // TODO: We shouldn't have to do this if the update is on a different root.
431 - // TODO: In the new system, we'll find a different bit that's not the one
432 - // we're currently rendering.
433 - if (
434 - workInProgressRoot !== null &&
435 - isSameExpirationTime(expirationTime, renderExpirationTime)
436 - ) {
437 - // This is a trick to move this update into a separate batch
438 - // TODO: This probably causes problems with ContinuousHydration and Idle
439 - expirationTime = bumpPriorityLower(expirationTime);
403 + if (
404 + // TODO: Temporary. We're removing the concept of discrete updates.
405 + (executionContext & DiscreteEventContext) !== NoContext &&
406 + schedulerPriority === UserBlockingSchedulerPriority
407 + ) {
408 + lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
409 + } else {
410 + const lanePriority = schedulerPriorityToLanePriority(schedulerPriority);
411 + lane = findUpdateLane(lanePriority, currentEventWipLanes);
412 + }
413 }
414
442 - return expirationTime;
415 + return lane;
416 }
417
445 -export function scheduleUpdateOnFiber(
446 - fiber: Fiber,
447 - expirationTime: ExpirationTimeOpaque,
448 -) {
418 +export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane) {
419 checkForNestedUpdates();
420 warnAboutRenderPhaseUpdatesInDEV(fiber);
421
452 - const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
422 + const root = markUpdateLaneFromFiberToRoot(fiber, lane);
423 if (root === null) {
424 warnAboutUpdateOnUnmountedFiberInDEV(fiber);
425 return null;
@@ -459,7 +429,7 @@ export function scheduleUpdateOnFiber(
429 // priority as an argument to that function and this one.
430 const priorityLevel = getCurrentPriorityLevel();
431
462 - if (isSameExpirationTime(expirationTime, (Sync: ExpirationTimeOpaque))) {
432 + if (lane === SyncLane) {
433 if (
434 // Check if we're inside unbatchedUpdates
435 (executionContext & LegacyUnbatchedContext) !== NoContext &&
@@ -467,7 +437,7 @@ export function scheduleUpdateOnFiber(
437 (executionContext & (RenderContext | CommitContext)) === NoContext
438 ) {
439 // Register pending interactions on the root to avoid losing traced interaction data.
470 - schedulePendingInteractions(root, expirationTime);
440 + schedulePendingInteractions(root, lane);
441
442 // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
443 // root inside of batchedUpdates should be synchronous, but layout updates
@@ -475,7 +445,7 @@ export function scheduleUpdateOnFiber(
445 performSyncWorkOnRoot(root);
446 } else {
447 ensureRootIsScheduled(root);
478 - schedulePendingInteractions(root, expirationTime);
448 + schedulePendingInteractions(root, lane);
449 if (executionContext === NoContext) {
450 // Flush the synchronous work now, unless we're already working or inside
451 // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
@@ -497,38 +467,37 @@ export function scheduleUpdateOnFiber(
467 // This is the result of a discrete event. Track the lowest priority
468 // discrete update per root so we can flush them early, if needed.
469 if (rootsWithPendingDiscreteUpdates === null) {
500 - rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
470 + rootsWithPendingDiscreteUpdates = new Set([root]);
471 } else {
502 - const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
503 - if (
504 - lastDiscreteTime === undefined ||
505 - !isSameOrHigherPriority(expirationTime, lastDiscreteTime)
506 - ) {
507 - rootsWithPendingDiscreteUpdates.set(root, expirationTime);
508 - }
472 + rootsWithPendingDiscreteUpdates.add(root);
473 }
474 }
475 // Schedule other updates after in case the callback is sync.
476 ensureRootIsScheduled(root);
513 - schedulePendingInteractions(root, expirationTime);
477 + schedulePendingInteractions(root, lane);
478 }
479 +
480 + // We use this when assigning a lane for a transition inside
481 + // `requestUpdateLane`. We assume it's the same as the root being updated,
482 + // since in the common case of a single root app it probably is. If it's not
483 + // the same root, then it's not a huge deal, we just might batch more stuff
484 + // together more than necessary.
485 + mostRecentlyUpdatedRoot = root;
486 }
487
488 // This is split into a separate function so we can mark a fiber with pending
489 // work without treating it as a typical update that originates from an event;
490 // e.g. retrying a Suspense boundary isn't an update, but it does schedule work
491 // on a fiber.
521 -function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
522 - // Update the source fiber's expiration time
523 - if (!isSameOrHigherPriority(fiber.expirationTime_opaque, expirationTime)) {
524 - fiber.expirationTime_opaque = expirationTime;
525 - }
492 +function markUpdateLaneFromFiberToRoot(
493 + fiber: Fiber,
494 + lane: Lane,
495 +): FiberRoot | null {
496 + // Update the source fiber's lanes
497 + fiber.lanes = mergeLanes(fiber.lanes, lane);
498 let alternate = fiber.alternate;
527 - if (
528 - alternate !== null &&
529 - !isSameOrHigherPriority(alternate.expirationTime_opaque, expirationTime)
530 - ) {
531 - alternate.expirationTime_opaque = expirationTime;
499 + if (alternate !== null) {
500 + alternate.lanes = mergeLanes(alternate.lanes, lane);
501 }
502 // Walk the parent path to the root and update the child expiration time.
503 let node = fiber.return;
@@ -538,27 +507,9 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
507 } else {
508 while (node !== null) {
509 alternate = node.alternate;
541 - if (
542 - !isSameOrHigherPriority(node.childExpirationTime_opaque, expirationTime)
543 - ) {
544 - node.childExpirationTime_opaque = expirationTime;
545 - if (
546 - alternate !== null &&
547 - !isSameOrHigherPriority(
548 - alternate.childExpirationTime_opaque,
549 - expirationTime,
550 - )
551 - ) {
552 - alternate.childExpirationTime_opaque = expirationTime;
553 - }
554 - } else if (
555 - alternate !== null &&
556 - !isSameOrHigherPriority(
557 - alternate.childExpirationTime_opaque,
558 - expirationTime,
559 - )
560 - ) {
561 - alternate.childExpirationTime_opaque = expirationTime;
510 + node.childLanes = mergeLanes(node.childLanes, lane);
511 + if (alternate !== null) {
512 + alternate.childLanes = mergeLanes(alternate.childLanes, lane);
513 }
514 if (node.return === null && node.tag === HostRoot) {
515 root = node.stateNode;
@@ -569,76 +520,30 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
520 }
521
522 if (root !== null) {
523 + // Mark that the root has a pending update.
524 + markRootUpdated(root, lane);
525 if (workInProgressRoot === root) {
526 // Received an update to a tree that's in the middle of rendering. Mark
574 - // that's unprocessed work on this root.
575 - markUnprocessedUpdateTime(expirationTime);
576 -
527 + // that there is unprocessed work on this root.
528 + workInProgressRootUpdatedLanes = mergeLanes(
529 + workInProgressRootUpdatedLanes,
530 + lane,
531 + );
532 if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
533 // The root already suspended with a delay, which means this render
534 // definitely won't finish. Since we have a new update, let's mark it as
535 // suspended now, right before marking the incoming update. This has the
536 // effect of interrupting the current render and switching to the update.
582 - // TODO: This happens to work when receiving an update during the render
583 - // phase, because of the trick inside requestUpdateExpirationTime to
584 - // subtract 1 from `renderExpirationTime` to move it into a
585 - // separate bucket. But we should probably model it with an exception,
586 - // using the same mechanism we use to force hydration of a subtree.
587 - // TODO: This does not account for low pri updates that were already
588 - // scheduled before the root started rendering. Need to track the next
589 - // pending expiration time (perhaps by backtracking the return path) and
590 - // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
591 - markRootSuspendedAtTime(root, renderExpirationTime);
537 + // TODO: Make sure this doesn't override pings that happen while we've
538 + // already started rendering.
539 + markRootSuspended(root, workInProgressRootRenderLanes);
540 }
541 }
594 - // Mark that the root has a pending update.
595 - markRootUpdatedAtTime(root, expirationTime);
542 }
543
544 return root;
545 }
546
601 -function getNextRootExpirationTimeToWorkOn(
602 - root: FiberRoot,
603 -): ExpirationTimeOpaque {
604 - // Determines the next expiration time that the root should render, taking
605 - // into account levels that may be suspended, or levels that may have
606 - // received a ping.
607 -
608 - const lastExpiredTime = root.lastExpiredTime_opaque;
609 - if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
610 - return lastExpiredTime;
611 - }
612 -
613 - // "Pending" refers to any update that hasn't committed yet, including if it
614 - // suspended. The "suspended" range is therefore a subset.
615 - const firstPendingTime = root.firstPendingTime_opaque;
616 - if (!isRootSuspendedAtTime(root, firstPendingTime)) {
617 - // The highest priority pending time is not suspended. Let's work on that.
618 - return firstPendingTime;
619 - }
620 -
621 - // If the first pending time is suspended, check if there's a lower priority
622 - // pending level that we know about. Or check if we received a ping. Work
623 - // on whichever is higher priority.
624 - const lastPingedTime = root.lastPingedTime_opaque;
625 - const nextKnownPendingLevel = root.nextKnownPendingLevel_opaque;
626 - const nextLevel = !isSameOrHigherPriority(
627 - nextKnownPendingLevel,
628 - lastPingedTime,
629 - )
630 - ? lastPingedTime
631 - : nextKnownPendingLevel;
632 - if (
633 - isSameOrHigherPriority((Idle: ExpirationTimeOpaque), nextLevel) &&
634 - !isSameExpirationTime(firstPendingTime, nextLevel)
635 - ) {
636 - // Don't work on Idle/Never priority unless everything else is committed.
637 - return NoWork;
638 - }
639 - return nextLevel;
640 -}
641 -
547 // Use this function to schedule a task for a root. There's only one task per
548 // root; if a task was already scheduled, we'll check to make sure the
549 // expiration time of the existing task is the same as the expiration time of
@@ -647,30 +552,31 @@ function getNextRootExpirationTimeToWorkOn(
552 function ensureRootIsScheduled(root: FiberRoot) {
553 const existingCallbackNode = root.callbackNode;
554
650 - const newCallbackId = getNextRootExpirationTimeToWorkOn(root);
651 - if (newCallbackId === (NoWork: ExpirationTimeOpaque)) {
555 + const newCallbackId = getNextLanes(
556 + root,
557 + root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
558 + );
559 + // This returns the priority level computed during the `getNextLanes` call.
560 + const newCallbackPriorityLevel = returnNextLanesPriority();
561 +
562 + if (newCallbackId === NoLanes) {
563 // Special case: There's nothing to work on.
564 if (existingCallbackNode !== null) {
565 cancelCallback(existingCallbackNode);
566 root.expiresAt = -1;
567 root.callbackNode = null;
568 root.callbackIsSync = false;
658 - root.callbackId = NoWork;
569 + root.callbackId = NoLanes;
570 }
571 return;
572 }
573
663 - const newTaskIsSync =
664 - newCallbackId === (Sync: ExpirationTimeOpaque) ||
665 - !isSameExpirationTime(
666 - root.lastExpiredTime_opaque,
667 - (NoWork: ExpirationTimeOpaque),
668 - );
574 + const newTaskIsSync = newCallbackPriorityLevel === SyncLanePriority;
575
576 // Check if there's an existing task. We may be able to reuse it.
577 const existingTaskId = root.callbackId;
578 const existingCallbackIsSync = root.callbackIsSync;
673 - if (existingTaskId !== (NoWork: ExpirationTimeOpaque)) {
579 + if (existingTaskId !== NoLanes) {
580 if (newCallbackId === existingTaskId) {
581 // This task is already scheduled. Let's check its priority.
582 if (
@@ -689,48 +595,19 @@ function ensureRootIsScheduled(root: FiberRoot) {
595 // Schedule a new callback.
596 let newCallbackNode;
597 if (newTaskIsSync) {
692 - // Special case: Sync React callbacks are scheduled on a special internal queue
598 + // Special case: Sync React callbacks are scheduled on a special
599 + // internal queue
600 newCallbackNode = scheduleSyncCallback(
601 performSyncWorkOnRoot.bind(null, root),
602 );
603 } else {
697 - // TODO: Use LanePriority instead of SchedulerPriority
698 - const priorityLevel = inferPriorityFromExpirationTime(newCallbackId);
699 - if (
700 - priorityLevel === NormalSchedulerPriority ||
701 - priorityLevel === UserBlockingSchedulerPriority
702 - ) {
703 - const existingExpirationTime = root.expiresAt;
704 - const currentTimeMs = now();
705 -
706 - // Compute an expiration time based on the priority level.
707 - const expiration =
708 - priorityLevel === UserBlockingSchedulerPriority ? 250 : 5000;
709 -
710 - let msUntilExpiration;
711 - if (existingExpirationTime === -1) {
712 - // This is the first concurrent update on the root. Use the expiration
713 - // time we just computed.
714 - msUntilExpiration = expiration;
715 - root.expiresAt = msUntilExpiration + currentTimeMs;
716 - } else {
717 - // There's already an expiration time. Use the smaller of the current
718 - // expiration and the one we just computed.
719 - msUntilExpiration = existingExpirationTime - currentTimeMs;
720 - if (expiration < msUntilExpiration) {
721 - root.expiresAt = expiration;
722 - }
723 - }
724 - newCallbackNode = scheduleCallback(
725 - priorityLevel,
726 - performConcurrentWorkOnRoot.bind(null, root),
727 - );
728 - } else {
729 - newCallbackNode = scheduleCallback(
730 - priorityLevel,
731 - performConcurrentWorkOnRoot.bind(null, root),
732 - );
733 - }
604 + const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
605 + newCallbackPriorityLevel,
606 + );
607 + newCallbackNode = scheduleCallback(
608 + schedulerPriorityLevel,
609 + performConcurrentWorkOnRoot.bind(null, root),
610 + );
611 }
612
613 root.callbackId = newCallbackId;
@@ -744,18 +621,40 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
621 // Since we know we're in a React event, we can clear the current
622 // event time. The next update will compute a new event time.
623 currentEventTime = -1;
624 + currentEventWipLanes = NoLanes;
625 + currentEventPendingLanes = NoLanes;
626
627 // Determine the next expiration time to work on, using the fields stored
628 // on the root.
750 - let expirationTime = getNextRootExpirationTimeToWorkOn(root);
751 - if (isSameExpirationTime(expirationTime, (NoWork: ExpirationTimeOpaque))) {
629 + let lanes = getNextLanes(
630 + root,
631 + root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
632 + );
633 + if (lanes === NoLanes) {
634 return null;
635 }
636
637 + // Check if any work has expired.
638 + const rootExpiresAt = root.expiresAt;
639 + if (rootExpiresAt !== -1 && rootExpiresAt < now()) {
640 + // Something expired. Flush synchronously until there's no expired
641 + // work left.
642 + // TODO: Should flush only the lanes that have expired, and maybe any lanes
643 + // that are higher priority than that.
644 + markRootExpired(root, lanes);
645 + // This will schedule a synchronous callback.
646 + ensureRootIsScheduled(root);
647 + return null;
648 + }
649 + // Similar branch, but for Scheduler.
650 + // TODO: This is only here to account for a Scheduler bug where `shouldYield`
651 + // sometimes returns `true` even if `didTimeout` is true, which leads to
652 + // an infinite loop. Once the bug in Scheduler is fixed, we can remove this,
653 + // since we track expiration times ourselves.
654 if (didTimeout) {
756 - // The render task took too long to complete. Mark the root as expired to
655 + // The Scheduler task took too long to complete. Mark the root as expired to
656 // prevent yielding to other tasks until this one finishes.
758 - markRootExpiredAtTime(root, expirationTime);
657 + markRootExpired(root, lanes);
658 // This will schedule a synchronous callback.
659 ensureRootIsScheduled(root);
660 return null;
@@ -769,28 +668,37 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
668
669 flushPassiveEffects();
670
772 - let exitStatus = renderRootConcurrent(root, expirationTime);
671 + let exitStatus = renderRootConcurrent(root, lanes);
672
774 - if (exitStatus !== RootIncomplete) {
673 + if (
674 + includesSomeLane(
675 + workInProgressRootIncludedLanes,
676 + workInProgressRootUpdatedLanes,
677 + )
678 + ) {
679 + // The render included lanes that were updated during the render phase.
680 + // For example, when unhiding a hidden tree, we include all the lanes
681 + // that were previously skipped when the tree was hidden. That set of
682 + // lanes is a superset of the lanes we started rendering with.
683 + //
684 + // So we'll throw out the current work and restart.
685 + prepareFreshStack(root, NoLanes);
686 + } else if (exitStatus !== RootIncomplete) {
687 if (exitStatus === RootErrored) {
776 - // If something threw an error, try rendering one more time. We'll
777 - // render synchronously to block concurrent data mutations, and we'll
778 - // render at Idle (or lower) so that all pending updates are included.
779 - // If it still fails after the second attempt, we'll give up and commit
780 - // the resulting tree.
781 - expirationTime = !isSameOrHigherPriority(
782 - (Idle: ExpirationTimeOpaque),
783 - expirationTime,
784 - )
785 - ? (Idle: ExpirationTimeOpaque)
786 - : expirationTime;
787 - exitStatus = renderRootSync(root, expirationTime);
688 + // If something threw an error, try rendering one more time. We'll render
689 + // synchronously to block concurrent data mutations, and we'll includes
690 + // all pending updates are included. If it still fails after the second
691 + // attempt, we'll give up and commit the resulting tree.
692 + lanes = getLanesToRetrySynchronouslyOnError(root);
693 + if (lanes !== NoLanes) {
694 + exitStatus = renderRootSync(root, lanes);
695 + }
696 }
697
698 if (exitStatus === RootFatalErrored) {
699 const fatalError = workInProgressRootFatalError;
792 - prepareFreshStack(root, expirationTime);
793 - markRootSuspendedAtTime(root, expirationTime);
700 + prepareFreshStack(root, NoLanes);
701 + markRootSuspended(root, lanes);
702 ensureRootIsScheduled(root);
703 throw fatalError;
704 }
@@ -799,11 +707,8 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
707 // or, if something suspended, wait to commit it after a timeout.
708 const finishedWork: Fiber = (root.current.alternate: any);
709 root.finishedWork = finishedWork;
802 - root.finishedExpirationTime_opaque = expirationTime;
803 - root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(
804 - finishedWork,
805 - );
806 - finishConcurrentRender(root, finishedWork, exitStatus, expirationTime);
710 + root.finishedLanes = lanes;
711 + finishConcurrentRender(root, finishedWork, exitStatus, lanes);
712 }
713
714 ensureRootIsScheduled(root);
@@ -815,12 +720,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
720 return null;
721 }
722
818 -function finishConcurrentRender(
819 - root,
820 - finishedWork,
821 - exitStatus,
822 - expirationTime,
823 -) {
723 +function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
724 switch (exitStatus) {
725 case RootIncomplete:
726 case RootFatalErrored: {
@@ -836,8 +736,7 @@ function finishConcurrentRender(
736 break;
737 }
738 case RootSuspended: {
839 - markRootSuspendedAtTime(root, expirationTime);
840 - const lastSuspendedTime = root.lastSuspendedTime_opaque;
739 + markRootSuspended(root, lanes);
740
741 // We have an acceptable loading state. We need to figure out if we
742 // should immediately commit it or wait a bit.
@@ -862,42 +761,18 @@ function finishConcurrentRender(
761 globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
762 // Don't bother with a very short suspense time.
763 if (msUntilTimeout > 10) {
865 - if (workInProgressRootHasPendingPing) {
866 - const lastPingedTime = root.lastPingedTime_opaque;
867 - if (
868 - isSameExpirationTime(
869 - lastPingedTime,
870 - (NoWork: ExpirationTimeOpaque),
871 - ) ||
872 - isSameOrHigherPriority(lastPingedTime, expirationTime)
873 - ) {
874 - // This render was pinged but we didn't get to restart
875 - // earlier so try restarting now instead.
876 - root.lastPingedTime_opaque = expirationTime;
877 - prepareFreshStack(root, expirationTime);
878 - break;
879 - }
880 - }
881 -
882 - const nextTime = getNextRootExpirationTimeToWorkOn(root);
883 - if (
884 - !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
885 - !isSameExpirationTime(nextTime, expirationTime)
886 - ) {
764 + const nextLanes = getNextLanes(root, NoLanes);
765 + if (nextLanes !== NoLanes) {
766 // There's additional work on this root.
767 break;
768 }
890 - if (
891 - !isSameExpirationTime(
892 - lastSuspendedTime,
893 - (NoWork: ExpirationTimeOpaque),
894 - ) &&
895 - !isSameExpirationTime(lastSuspendedTime, expirationTime)
896 - ) {
769 + const suspendedLanes = root.suspendedLanes;
770 + if (!isSubsetOfLanes(suspendedLanes, lanes)) {
771 // We should prefer to render the fallback of at the last
772 // suspended level. Ping the last suspended level to try
773 // rendering it again.
900 - root.lastPingedTime_opaque = lastSuspendedTime;
774 + // FIXME: What if the suspended lanes are Idle? Should not restart.
775 + markRootPinged(root, suspendedLanes);
776 break;
777 }
778
@@ -916,8 +791,7 @@ function finishConcurrentRender(
791 break;
792 }
793 case RootSuspendedWithDelay: {
919 - markRootSuspendedAtTime(root, expirationTime);
920 - const lastSuspendedTime = root.lastSuspendedTime_opaque;
794 + markRootSuspended(root, lanes);
795
796 if (
797 // do not delay if we're inside an act() scope
@@ -925,42 +799,18 @@ function finishConcurrentRender(
799 ) {
800 // We're suspended in a state that should be avoided. We'll try to
801 // avoid committing it for as long as the timeouts let us.
928 - if (workInProgressRootHasPendingPing) {
929 - const lastPingedTime = root.lastPingedTime_opaque;
930 - if (
931 - isSameExpirationTime(
932 - lastPingedTime,
933 - (NoWork: ExpirationTimeOpaque),
934 - ) ||
935 - isSameOrHigherPriority(lastPingedTime, expirationTime)
936 - ) {
937 - // This render was pinged but we didn't get to restart earlier
938 - // so try restarting now instead.
939 - root.lastPingedTime_opaque = expirationTime;
940 - prepareFreshStack(root, expirationTime);
941 - break;
942 - }
943 - }
944 -
945 - const nextTime = getNextRootExpirationTimeToWorkOn(root);
946 - if (
947 - !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
948 - !isSameExpirationTime(nextTime, expirationTime)
949 - ) {
802 + const nextLanes = getNextLanes(root, NoLanes);
803 + if (nextLanes !== NoLanes) {
804 // There's additional work on this root.
805 break;
806 }
953 - if (
954 - !isSameExpirationTime(
955 - lastSuspendedTime,
956 - (NoWork: ExpirationTimeOpaque),
957 - ) &&
958 - !isSameExpirationTime(lastSuspendedTime, expirationTime)
959 - ) {
807 + const suspendedLanes = root.suspendedLanes;
808 + if (!isSubsetOfLanes(suspendedLanes, lanes)) {
809 // We should prefer to render the fallback of at the last
810 // suspended level. Ping the last suspended level to try
811 // rendering it again.
963 - root.lastPingedTime_opaque = lastSuspendedTime;
812 + // FIXME: What if the suspended lanes are Idle? Should not restart.
813 + markRootPinged(root, suspendedLanes);
814 break;
815 }
816
@@ -1015,7 +865,7 @@ function finishConcurrentRender(
865 workInProgressRootCanSuspendUsingConfig,
866 );
867 if (msUntilTimeout > 10) {
1018 - markRootSuspendedAtTime(root, expirationTime);
868 + markRootSuspended(root, lanes);
869 root.timeoutHandle = scheduleTimeout(
870 commitRoot.bind(null, root),
871 msUntilTimeout,
@@ -1032,6 +882,16 @@ function finishConcurrentRender(
882 }
883 }
884
885 +function markRootSuspended(root, suspendedLanes) {
886 + // When suspending, we should always exclude lanes that were pinged or (more
887 + // rarely, since we try to avoid it) updated during the render phase.
888 + // TODO: Lol maybe there's a better way to factor this besides this
889 + // obnoxiously named function :)
890 + suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
891 + suspendedLanes = removeLanes(suspendedLanes, workInProgressRootUpdatedLanes);
892 + markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes);
893 +}
894 +
895 // This is the entry point for synchronous tasks that don't go
896 // through Scheduler
897 function performSyncWorkOnRoot(root) {
@@ -1042,50 +902,53 @@ function performSyncWorkOnRoot(root) {
902
903 flushPassiveEffects();
904
1045 - const lastExpiredTime = root.lastExpiredTime_opaque;
1046 -
1047 - let expirationTime;
1048 - if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
1049 - // There's expired work on this root. Check if we have a partial tree
1050 - // that we can reuse.
905 + let lanes;
906 + let exitStatus;
907 + if (
908 + root === workInProgressRoot &&
909 + includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
910 + ) {
911 + // There's a partial tree, and at least one of its lanes has expired. Finish
912 + // rendering it before rendering the rest of the expired work.
913 + lanes = workInProgressRootRenderLanes;
914 + exitStatus = renderRootSync(root, lanes);
915 if (
1052 - root === workInProgressRoot &&
1053 - isSameOrHigherPriority(renderExpirationTime, lastExpiredTime)
916 + includesSomeLane(
917 + workInProgressRootIncludedLanes,
918 + workInProgressRootUpdatedLanes,
919 + )
920 ) {
1055 - // There's a partial tree with equal or greater than priority than the
1056 - // expired level. Finish rendering it before rendering the rest of the
1057 - // expired work.
1058 - expirationTime = renderExpirationTime;
1059 - } else {
1060 - // Start a fresh tree.
1061 - expirationTime = lastExpiredTime;
921 + // The render included lanes that were updated during the render phase.
922 + // For example, when unhiding a hidden tree, we include all the lanes
923 + // that were previously skipped when the tree was hidden. That set of
924 + // lanes is a superset of the lanes we started rendering with.
925 + //
926 + // Note that this only happens when part of the tree is rendered
927 + // concurrently. If the whole tree is rendered synchronously, then there
928 + // are no interleaved events.
929 + lanes = getNextLanes(root, lanes);
930 + exitStatus = renderRootSync(root, lanes);
931 }
932 } else {
1064 - // There's no expired work. This must be a new, synchronous render.
1065 - expirationTime = Sync;
933 + lanes = getNextLanes(root, NoLanes);
934 + exitStatus = renderRootSync(root, lanes);
935 }
936
1068 - let exitStatus = renderRootSync(root, expirationTime);
1069 -
937 if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
1071 - // If something threw an error, try rendering one more time. We'll
1072 - // render synchronously to block concurrent data mutations, and we'll
1073 - // render at Idle (or lower) so that all pending updates are included.
1074 - // If it still fails after the second attempt, we'll give up and commit
1075 - // the resulting tree.
1076 - expirationTime = !isSameOrHigherPriority(
1077 - (Idle: ExpirationTimeOpaque),
1078 - expirationTime,
1079 - )
1080 - ? (Idle: ExpirationTimeOpaque)
1081 - : expirationTime;
1082 - exitStatus = renderRootSync(root, expirationTime);
938 + // If something threw an error, try rendering one more time. We'll render
939 + // synchronously to block concurrent data mutations, and we'll includes
940 + // all pending updates are included. If it still fails after the second
941 + // attempt, we'll give up and commit the resulting tree.
942 + lanes = getLanesToRetrySynchronouslyOnError(root);
943 + if (lanes !== NoLanes) {
944 + exitStatus = renderRootSync(root, lanes);
945 + }
946 }
947
948 if (exitStatus === RootFatalErrored) {
949 const fatalError = workInProgressRootFatalError;
1087 - prepareFreshStack(root, expirationTime);
1088 - markRootSuspendedAtTime(root, expirationTime);
950 + prepareFreshStack(root, NoLanes);
951 + markRootSuspended(root, lanes);
952 ensureRootIsScheduled(root);
953 throw fatalError;
954 }
@@ -1094,8 +957,7 @@ function performSyncWorkOnRoot(root) {
957 // will commit it even if something suspended.
958 const finishedWork: Fiber = (root.current.alternate: any);
959 root.finishedWork = finishedWork;
1097 - root.finishedExpirationTime_opaque = expirationTime;
1098 - root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(finishedWork);
960 + root.finishedLanes = lanes;
961 commitRoot(root);
962
963 // Before exiting, make sure there's a callback scheduled for the next
@@ -1105,11 +967,8 @@ function performSyncWorkOnRoot(root) {
967 return null;
968 }
969
1108 -export function flushRoot(
1109 - root: FiberRoot,
1110 - expirationTime: ExpirationTimeOpaque,
1111 -) {
1112 - markRootExpiredAtTime(root, expirationTime);
970 +export function flushRoot(root: FiberRoot, lanes: Lanes) {
971 + markRootExpired(root, lanes);
972 ensureRootIsScheduled(root);
973 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
974 flushSyncCallbackQueue();
@@ -1164,8 +1023,8 @@ function flushPendingDiscreteUpdates() {
1023 // immediately flush them.
1024 const roots = rootsWithPendingDiscreteUpdates;
1025 rootsWithPendingDiscreteUpdates = null;
1167 - roots.forEach((expirationTime, root) => {
1168 - markRootExpiredAtTime(root, expirationTime);
1026 + roots.forEach(root => {
1027 + markDiscreteUpdatesExpired(root);
1028 ensureRootIsScheduled(root);
1029 });
1030 }
@@ -1278,22 +1137,23 @@ export function flushControlled(fn: () => mixed): void {
1137 }
1138 }
1139
1281 -export function pushRenderExpirationTime(
1282 - fiber: Fiber,
1283 - subtreeRenderTime: ExpirationTimeOpaque,
1284 -) {
1285 - pushToStack(renderExpirationTimeCursor, renderExpirationTime, fiber);
1286 - renderExpirationTime = subtreeRenderTime;
1140 +export function pushRenderLanes(fiber: Fiber, lanes: Lanes) {
1141 + pushToStack(subtreeRenderLanesCursor, subtreeRenderLanes, fiber);
1142 + subtreeRenderLanes = mergeLanes(subtreeRenderLanes, lanes);
1143 + workInProgressRootIncludedLanes = mergeLanes(
1144 + workInProgressRootIncludedLanes,
1145 + lanes,
1146 + );
1147 }
1148
1289 -export function popRenderExpirationTime(fiber: Fiber) {
1290 - renderExpirationTime = renderExpirationTimeCursor.current;
1291 - popFromStack(renderExpirationTimeCursor, fiber);
1149 +export function popRenderLanes(fiber: Fiber) {
1150 + subtreeRenderLanes = subtreeRenderLanesCursor.current;
1151 + popFromStack(subtreeRenderLanesCursor, fiber);
1152 }
1153
1294 -function prepareFreshStack(root, expirationTime) {
1154 +function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1155 root.finishedWork = null;
1296 - root.finishedExpirationTime_opaque = NoWork;
1156 + root.finishedLanes = NoLanes;
1157
1158 const timeoutHandle = root.timeoutHandle;
1159 if (timeoutHandle !== noTimeout) {
@@ -1304,27 +1164,6 @@ function prepareFreshStack(root, expirationTime) {
1164 cancelTimeout(timeoutHandle);
1165 }
1166
1307 - // Check if there's a suspended level at lower priority.
1308 - const lastSuspendedTime = root.lastSuspendedTime_opaque;
1309 - if (
1310 - !isSameExpirationTime(lastSuspendedTime, (NoWork: ExpirationTimeOpaque)) &&
1311 - !isSameOrHigherPriority(lastSuspendedTime, expirationTime)
1312 - ) {
1313 - const lastPingedTime = root.lastPingedTime_opaque;
1314 - // Make sure the suspended level is marked as pinged so that we return back
1315 - // to it later, in case the render we're about to start gets aborted.
1316 - // Generally we only reach this path via a ping, but we shouldn't assume
1317 - // that will always be the case.
1318 - // Note: This is defensive coding to prevent a pending commit from
1319 - // being dropped without being rescheduled. It shouldn't be necessary.
1320 - if (
1321 - isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) ||
1322 - !isSameOrHigherPriority(lastSuspendedTime, lastPingedTime)
1323 - ) {
1324 - root.lastPingedTime_opaque = lastSuspendedTime;
1325 - }
1326 - }
1327 -
1167 if (workInProgress !== null) {
1168 let interruptedWork = workInProgress.return;
1169 while (interruptedWork !== null) {
@@ -1334,14 +1173,15 @@ function prepareFreshStack(root, expirationTime) {
1173 }
1174 workInProgressRoot = root;
1175 workInProgress = createWorkInProgress(root.current, null);
1337 - renderExpirationTime = expirationTime;
1176 + workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1177 workInProgressRootExitStatus = RootIncomplete;
1178 workInProgressRootFatalError = null;
1179 workInProgressRootLatestProcessedEventTime = -1;
1180 workInProgressRootLatestSuspenseTimeout = -1;
1181 workInProgressRootCanSuspendUsingConfig = null;
1343 - workInProgressRootNextUnprocessedUpdateTime = NoWork;
1344 - workInProgressRootHasPendingPing = false;
1182 + workInProgressRootSkippedLanes = NoLanes;
1183 + workInProgressRootUpdatedLanes = NoLanes;
1184 + workInProgressRootPingedLanes = NoLanes;
1185
1186 if (enableSchedulerTracing) {
1187 spawnedWorkDuringRender = null;
@@ -1393,7 +1233,7 @@ function handleError(root, thrownValue): void {
1233 erroredWork.return,
1234 erroredWork,
1235 thrownValue,
1396 - renderExpirationTime,
1236 + workInProgressRootRenderLanes,
1237 );
1238 completeUnitOfWork(erroredWork);
1239 } catch (yetAnotherThrownValue) {
@@ -1479,17 +1319,11 @@ export function markRenderEventTimeAndConfig(
1319 }
1320 }
1321
1482 -export function markUnprocessedUpdateTime(
1483 - expirationTime: ExpirationTimeOpaque,
1484 -): void {
1485 - if (
1486 - !isSameOrHigherPriority(
1487 - workInProgressRootNextUnprocessedUpdateTime,
1488 - expirationTime,
1489 - )
1490 - ) {
1491 - workInProgressRootNextUnprocessedUpdateTime = expirationTime;
1492 - }
1322 +export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
1323 + workInProgressRootSkippedLanes = mergeLanes(
1324 + lane,
1325 + workInProgressRootSkippedLanes,
1326 + );
1327 }
1328
1329 export function renderDidSuspend(): void {
@@ -1506,23 +1340,21 @@ export function renderDidSuspendDelayIfPossible(): void {
1340 workInProgressRootExitStatus = RootSuspendedWithDelay;
1341 }
1342
1509 - // Check if there's a lower priority update somewhere else in the tree.
1343 + // Check if there are updates that we skipped tree that might have unblocked
1344 + // this render.
1345 if (
1511 - !isSameExpirationTime(
1512 - workInProgressRootNextUnprocessedUpdateTime,
1513 - (NoWork: ExpirationTimeOpaque),
1514 - ) &&
1515 - workInProgressRoot !== null
1346 + workInProgressRoot !== null &&
1347 + (hasUpdatePriority(workInProgressRootSkippedLanes) ||
1348 + hasUpdatePriority(workInProgressRootUpdatedLanes))
1349 ) {
1517 - // Mark the current render as suspended, and then mark that there's a
1518 - // pending update.
1519 - // TODO: This should immediately interrupt the current render, instead
1520 - // of waiting until the next time we yield.
1521 - markRootSuspendedAtTime(workInProgressRoot, renderExpirationTime);
1522 - markRootUpdatedAtTime(
1523 - workInProgressRoot,
1524 - workInProgressRootNextUnprocessedUpdateTime,
1525 - );
1350 + // Mark the current render as suspended so that we switch to working on
1351 + // the updates that were skipped. Usually we only suspend at the end of
1352 + // the render phase.
1353 + // TODO: We should probably always mark the root as suspended immediately
1354 + // (inside this function), since by suspending at the end of the render
1355 + // phase introduces a potential mistake where we suspend lanes that were
1356 + // pinged or updated while we were rendering.
1357 + markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes);
1358 }
1359 }
1360
@@ -1540,19 +1372,16 @@ export function renderHasNotSuspendedYet(): boolean {
1372 return workInProgressRootExitStatus === RootIncomplete;
1373 }
1374
1543 -function renderRootSync(root, expirationTime) {
1375 +function renderRootSync(root: FiberRoot, lanes: Lanes) {
1376 const prevExecutionContext = executionContext;
1377 executionContext |= RenderContext;
1378 const prevDispatcher = pushDispatcher(root);
1379
1548 - // If the root or expiration time have changed, throw out the existing stack
1380 + // If the root or lanes have changed, throw out the existing stack
1381 // and prepare a fresh one. Otherwise we'll continue where we left off.
1550 - if (
1551 - root !== workInProgressRoot ||
1552 - !isSameExpirationTime(expirationTime, renderExpirationTime)
1553 - ) {
1554 - prepareFreshStack(root, expirationTime);
1555 - startWorkOnPendingInteractions(root, expirationTime);
1382 + if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1383 + prepareFreshStack(root, lanes);
1384 + startWorkOnPendingInteractions(root, lanes);
1385 }
1386
1387 const prevInteractions = pushInteractions(root);
@@ -1584,6 +1413,7 @@ function renderRootSync(root, expirationTime) {
1413
1414 // Set this to null to indicate there's no in-progress render.
1415 workInProgressRoot = null;
1416 + workInProgressRootRenderLanes = NoLanes;
1417
1418 return workInProgressRootExitStatus;
1419 }
@@ -1597,19 +1427,16 @@ function workLoopSync() {
1427 }
1428 }
1429
1600 -function renderRootConcurrent(root, expirationTime) {
1430 +function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
1431 const prevExecutionContext = executionContext;
1432 executionContext |= RenderContext;
1433 const prevDispatcher = pushDispatcher(root);
1434
1605 - // If the root or expiration time have changed, throw out the existing stack
1435 + // If the root or lanes have changed, throw out the existing stack
1436 // and prepare a fresh one. Otherwise we'll continue where we left off.
1607 - if (
1608 - root !== workInProgressRoot ||
1609 - !isSameExpirationTime(expirationTime, renderExpirationTime)
1610 - ) {
1611 - prepareFreshStack(root, expirationTime);
1612 - startWorkOnPendingInteractions(root, expirationTime);
1437 + if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1438 + prepareFreshStack(root, lanes);
1439 + startWorkOnPendingInteractions(root, lanes);
1440 }
1441
1442 const prevInteractions = pushInteractions(root);
@@ -1638,6 +1465,7 @@ function renderRootConcurrent(root, expirationTime) {
1465 // Completed the tree.
1466 // Set this to null to indicate there's no in-progress render.
1467 workInProgressRoot = null;
1468 + workInProgressRootRenderLanes = NoLanes;
1469
1470 // Return the final exit status.
1471 return workInProgressRootExitStatus;
@@ -1662,10 +1490,10 @@ function performUnitOfWork(unitOfWork: Fiber): void {
1490 let next;
1491 if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
1492 startProfilerTimer(unitOfWork);
1665 - next = beginWork(current, unitOfWork, renderExpirationTime);
1493 + next = beginWork(current, unitOfWork, subtreeRenderLanes);
1494 stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
1495 } else {
1668 - next = beginWork(current, unitOfWork, renderExpirationTime);
1496 + next = beginWork(current, unitOfWork, subtreeRenderLanes);
1497 }
1498
1499 resetCurrentDebugFiberInDEV();
@@ -1699,15 +1527,15 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1527 !enableProfilerTimer ||
1528 (completedWork.mode & ProfileMode) === NoMode
1529 ) {
1702 - next = completeWork(current, completedWork, renderExpirationTime);
1530 + next = completeWork(current, completedWork, subtreeRenderLanes);
1531 } else {
1532 startProfilerTimer(completedWork);
1705 - next = completeWork(current, completedWork, renderExpirationTime);
1533 + next = completeWork(current, completedWork, subtreeRenderLanes);
1534 // Update render duration assuming we didn't error.
1535 stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1536 }
1537 resetCurrentDebugFiberInDEV();
1710 - resetChildExpirationTime(completedWork);
1538 + resetChildLanes(completedWork);
1539
1540 if (next !== null) {
1541 // Completing this fiber spawned new work. Work on that next.
@@ -1757,7 +1585,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1585 // This fiber did not complete because something threw. Pop values off
1586 // the stack without entering the complete phase. If this is a boundary,
1587 // capture values if possible.
1760 - const next = unwindWork(completedWork, renderExpirationTime);
1588 + const next = unwindWork(completedWork, subtreeRenderLanes);
1589
1590 // Because this fiber did not complete, don't reset its expiration time.
1591
@@ -1813,31 +1641,21 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1641 }
1642 }
1643
1816 -function getRemainingExpirationTime(fiber: Fiber) {
1817 - const updateExpirationTime = fiber.expirationTime_opaque;
1818 - const childExpirationTime = fiber.childExpirationTime_opaque;
1819 - return !isSameOrHigherPriority(childExpirationTime, updateExpirationTime)
1820 - ? updateExpirationTime
1821 - : childExpirationTime;
1822 -}
1823 -
1824 -function resetChildExpirationTime(completedWork: Fiber) {
1644 +function resetChildLanes(completedWork: Fiber) {
1645 if (
1826 - !isSameExpirationTime(
1827 - renderExpirationTime,
1828 - (Never: ExpirationTimeOpaque),
1829 - ) &&
1830 - isSameExpirationTime(
1831 - completedWork.childExpirationTime_opaque,
1832 - (Never: ExpirationTimeOpaque),
1833 - )
1646 + // TODO: Move this check out of the hot path by moving `resetChildLanes`
1647 + // to switch statement in `completeWork`.
1648 + (completedWork.tag === LegacyHiddenComponent ||
1649 + completedWork.tag === OffscreenComponent) &&
1650 + completedWork.memoizedState !== null &&
1651 + !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))
1652 ) {
1653 // The children of this component are hidden. Don't bubble their
1654 // expiration times.
1655 return;
1656 }
1657
1840 - let newChildExpirationTime = NoWork;
1658 + let newChildLanes = NoLanes;
1659
1660 // Bubble up the earliest expiration time.
1661 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
@@ -1859,24 +1677,10 @@ function resetChildExpirationTime(completedWork: Fiber) {
1677
1678 let child = completedWork.child;
1679 while (child !== null) {
1862 - const childUpdateExpirationTime = child.expirationTime_opaque;
1863 - const childChildExpirationTime = child.childExpirationTime_opaque;
1864 - if (
1865 - !isSameOrHigherPriority(
1866 - newChildExpirationTime,
1867 - childUpdateExpirationTime,
1868 - )
1869 - ) {
1870 - newChildExpirationTime = childUpdateExpirationTime;
1871 - }
1872 - if (
1873 - !isSameOrHigherPriority(
1874 - newChildExpirationTime,
1875 - childChildExpirationTime,
1876 - )
1877 - ) {
1878 - newChildExpirationTime = childChildExpirationTime;
1879 - }
1680 + newChildLanes = mergeLanes(
1681 + newChildLanes,
1682 + mergeLanes(child.lanes, child.childLanes),
1683 + );
1684 if (shouldBubbleActualDurations) {
1685 actualDuration += child.actualDuration;
1686 }
@@ -1888,29 +1692,15 @@ function resetChildExpirationTime(completedWork: Fiber) {
1692 } else {
1693 let child = completedWork.child;
1694 while (child !== null) {
1891 - const childUpdateExpirationTime = child.expirationTime_opaque;
1892 - const childChildExpirationTime = child.childExpirationTime_opaque;
1893 - if (
1894 - !isSameOrHigherPriority(
1895 - newChildExpirationTime,
1896 - childUpdateExpirationTime,
1897 - )
1898 - ) {
1899 - newChildExpirationTime = childUpdateExpirationTime;
1900 - }
1901 - if (
1902 - !isSameOrHigherPriority(
1903 - newChildExpirationTime,
1904 - childChildExpirationTime,
1905 - )
1906 - ) {
1907 - newChildExpirationTime = childChildExpirationTime;
1908 - }
1695 + newChildLanes = mergeLanes(
1696 + newChildLanes,
1697 + mergeLanes(child.lanes, child.childLanes),
1698 + );
1699 child = child.sibling;
1700 }
1701 }
1702
1913 - completedWork.childExpirationTime_opaque = newChildExpirationTime;
1703 + completedWork.childLanes = newChildLanes;
1704 }
1705
1706 function commitRoot(root) {
@@ -1940,12 +1730,12 @@ function commitRootImpl(root, renderPriorityLevel) {
1730 );
1731
1732 const finishedWork = root.finishedWork;
1943 - const expirationTime = root.finishedExpirationTime_opaque;
1733 + const lanes = root.finishedLanes;
1734 if (finishedWork === null) {
1735 return null;
1736 }
1737 root.finishedWork = null;
1948 - root.finishedExpirationTime_opaque = NoWork;
1738 + root.finishedLanes = NoLanes;
1739
1740 invariant(
1741 finishedWork !== root.current,
@@ -1956,7 +1746,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1746 // commitRoot never returns a continuation; it always finishes synchronously.
1747 // So we can clear these now to allow a new callback to be scheduled.
1748 root.callbackNode = null;
1959 - root.callbackId = NoWork;
1749 + root.callbackId = NoLanes;
1750 // TODO: Use LanePriority instead of SchedulerPriority
1751 if (renderPriorityLevel < ImmediateSchedulerPriority) {
1752 // If this was a concurrent render, we can reset the expiration time.
@@ -1965,26 +1755,16 @@ function commitRootImpl(root, renderPriorityLevel) {
1755
1756 // Update the first and last pending times on this root. The new first
1757 // pending time is whatever is left on the root fiber.
1968 - const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
1969 - finishedWork,
1970 - );
1971 - markRootFinishedAtTime(
1972 - root,
1973 - expirationTime,
1974 - remainingExpirationTimeBeforeCommit,
1975 - );
1758 + let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
1759 + markRootFinished(root, remainingLanes);
1760
1761 // Clear already finished discrete updates in case that a later call of
1762 // `flushDiscreteUpdates` starts a useless render pass which may cancels
1763 // a scheduled timeout.
1764 if (rootsWithPendingDiscreteUpdates !== null) {
1981 - const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
1765 if (
1983 - lastDiscreteTime !== undefined &&
1984 - !isSameOrHigherPriority(
1985 - remainingExpirationTimeBeforeCommit,
1986 - lastDiscreteTime,
1987 - )
1766 + !hasDiscreteLanes(remainingLanes) &&
1767 + rootsWithPendingDiscreteUpdates.has(root)
1768 ) {
1769 rootsWithPendingDiscreteUpdates.delete(root);
1770 }
@@ -1994,7 +1774,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1774 // We can reset these now that they are finished.
1775 workInProgressRoot = null;
1776 workInProgress = null;
1997 - renderExpirationTime = NoWork;
1777 + workInProgressRootRenderLanes = NoLanes;
1778 } else {
1779 // This indicates that the last root we worked on is not the same one that
1780 // we're committing now. This most commonly happens when a suspended root
@@ -2112,13 +1892,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1892 nextEffect = firstEffect;
1893 do {
1894 if (__DEV__) {
2115 - invokeGuardedCallback(
2116 - null,
2117 - commitLayoutEffects,
2118 - null,
2119 - root,
2120 - expirationTime,
2121 - );
1895 + invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
1896 if (hasCaughtError()) {
1897 invariant(nextEffect !== null, 'Should be working on an effect.');
1898 const error = clearCaughtError();
@@ -2127,7 +1901,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1901 }
1902 } else {
1903 try {
2130 - commitLayoutEffects(root, expirationTime);
1904 + commitLayoutEffects(root, lanes);
1905 } catch (error) {
1906 invariant(nextEffect !== null, 'Should be working on an effect.');
1907 captureCommitPhaseError(nextEffect, error);
@@ -2164,7 +1938,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1938 // schedule a callback until after flushing layout work.
1939 rootDoesHavePassiveEffects = false;
1940 rootWithPendingPassiveEffects = root;
2167 - pendingPassiveEffectsExpirationTime = expirationTime;
1941 + pendingPassiveEffectsLanes = lanes;
1942 pendingPassiveEffectsRenderPriority = renderPriorityLevel;
1943 } else {
1944 // We are done with the effect chain at this point so let's clear the
@@ -2178,14 +1952,11 @@ function commitRootImpl(root, renderPriorityLevel) {
1952 }
1953 }
1954
1955 + // Read this again, since an effect might have updated it
1956 + remainingLanes = root.pendingLanes;
1957 +
1958 // Check if there's remaining work on this root
2182 - const remainingExpirationTime = root.firstPendingTime_opaque;
2183 - if (
2184 - !isSameExpirationTime(
2185 - remainingExpirationTime,
2186 - (NoWork: ExpirationTimeOpaque),
2187 - )
2188 - ) {
1959 + if (remainingLanes !== NoLanes) {
1960 if (enableSchedulerTracing) {
1961 if (spawnedWorkDuringRender !== null) {
1962 const expirationTimes = spawnedWorkDuringRender;
@@ -2198,7 +1969,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1969 );
1970 }
1971 }
2201 - schedulePendingInteractions(root, remainingExpirationTime);
1972 + schedulePendingInteractions(root, remainingLanes);
1973 }
1974 } else {
1975 // If there's no remaining work, we can clear the set of already failed
@@ -2212,13 +1983,11 @@ function commitRootImpl(root, renderPriorityLevel) {
1983 // Otherwise, we'll wait until after the passive effects are flushed.
1984 // Wait to do this until after remaining work has been scheduled,
1985 // so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
2215 - finishPendingInteractions(root, expirationTime);
1986 + finishPendingInteractions(root, lanes);
1987 }
1988 }
1989
2219 - if (
2220 - isSameExpirationTime(remainingExpirationTime, (Sync: ExpirationTimeOpaque))
2221 - ) {
1990 + if (remainingLanes === SyncLane) {
1991 // Count the number of times the root synchronously re-renders without
1992 // finishing. If there are too many, it indicates an infinite update loop.
1993 if (root === rootWithNestedUpdates) {
@@ -2366,10 +2135,7 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
2135 }
2136 }
2137
2369 -function commitLayoutEffects(
2370 - root: FiberRoot,
2371 - committedExpirationTime: ExpirationTimeOpaque,
2372 -) {
2138 +function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2139 // TODO: Should probably move the bulk of this function to commitWork.
2140 while (nextEffect !== null) {
2141 setCurrentDebugFiberInDEV(nextEffect);
@@ -2378,12 +2144,7 @@ function commitLayoutEffects(
2144
2145 if (effectTag & (Update | Callback)) {
2146 const current = nextEffect.alternate;
2381 - commitLayoutEffectOnFiber(
2382 - root,
2383 - current,
2384 - nextEffect,
2385 - committedExpirationTime,
2386 - );
2147 + commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
2148 }
2149
2150 if (effectTag & Ref) {
@@ -2471,9 +2232,9 @@ function flushPassiveEffectsImpl() {
2232 }
2233
2234 const root = rootWithPendingPassiveEffects;
2474 - const expirationTime = pendingPassiveEffectsExpirationTime;
2235 + const lanes = pendingPassiveEffectsLanes;
2236 rootWithPendingPassiveEffects = null;
2476 - pendingPassiveEffectsExpirationTime = NoWork;
2237 + pendingPassiveEffectsLanes = NoLanes;
2238
2239 invariant(
2240 (executionContext & (RenderContext | CommitContext)) === NoContext,
@@ -2651,7 +2412,7 @@ function flushPassiveEffectsImpl() {
2412
2413 if (enableSchedulerTracing) {
2414 popInteractions(((prevInteractions: any): Set<Interaction>));
2654 - finishPendingInteractions(root, expirationTime);
2415 + finishPendingInteractions(root, lanes);
2416 }
2417
2418 if (__DEV__) {
@@ -2699,19 +2460,12 @@ function captureCommitPhaseErrorOnRoot(
2460 error: mixed,
2461 ) {
2462 const errorInfo = createCapturedValue(error, sourceFiber);
2702 - const update = createRootErrorUpdate(
2703 - rootFiber,
2704 - errorInfo,
2705 - (Sync: ExpirationTimeOpaque),
2706 - );
2463 + const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2464 enqueueUpdate(rootFiber, update);
2708 - const root = markUpdateTimeFromFiberToRoot(
2709 - rootFiber,
2710 - (Sync: ExpirationTimeOpaque),
2711 - );
2465 + const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2466 if (root !== null) {
2467 ensureRootIsScheduled(root);
2714 - schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
2468 + schedulePendingInteractions(root, SyncLane);
2469 }
2470 }
2471
@@ -2740,16 +2494,13 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2494 const update = createClassErrorUpdate(
2495 fiber,
2496 errorInfo,
2743 - (Sync: ExpirationTimeOpaque),
2497 + (SyncLane: Lane),
2498 );
2499 enqueueUpdate(fiber, update);
2746 - const root = markUpdateTimeFromFiberToRoot(
2747 - fiber,
2748 - (Sync: ExpirationTimeOpaque),
2749 - );
2500 + const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2501 if (root !== null) {
2502 ensureRootIsScheduled(root);
2752 - schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
2503 + schedulePendingInteractions(root, SyncLane);
2504 }
2505 return;
2506 }
@@ -2761,7 +2512,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2512 export function pingSuspendedRoot(
2513 root: FiberRoot,
2514 wakeable: Wakeable,
2764 - suspendedTime: ExpirationTimeOpaque,
2515 + pingedLanes: Lanes,
2516 ) {
2517 const pingCache = root.pingCache;
2518 if (pingCache !== null) {
@@ -2770,9 +2521,11 @@ export function pingSuspendedRoot(
2521 pingCache.delete(wakeable);
2522 }
2523
2524 + markRootPinged(root, pingedLanes);
2525 +
2526 if (
2527 workInProgressRoot === root &&
2775 - isSameExpirationTime(renderExpirationTime, suspendedTime)
2528 + isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
2529 ) {
2530 // Received a ping at the same priority level at which we're currently
2531 // rendering. We might want to restart this render. This should mirror
@@ -2793,69 +2546,52 @@ export function pingSuspendedRoot(
2546 workInProgressRootLatestProcessedEventTime === -1 &&
2547 now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2548 ) {
2796 - // Restart from the root. Don't need to schedule a ping because
2797 - // we're already working on this tree.
2798 - prepareFreshStack(root, renderExpirationTime);
2549 + // Restart from the root.
2550 + prepareFreshStack(root, NoLanes);
2551 } else {
2552 // Even though we can't restart right now, we might get an
2553 // opportunity later. So we mark this render as having a ping.
2802 - workInProgressRootHasPendingPing = true;
2554 + workInProgressRootPingedLanes = mergeLanes(
2555 + workInProgressRootPingedLanes,
2556 + pingedLanes,
2557 + );
2558 }
2804 - return;
2805 - }
2806 -
2807 - if (!isRootSuspendedAtTime(root, suspendedTime)) {
2808 - // The root is no longer suspended at this time.
2809 - return;
2559 }
2560
2812 - const lastPingedTime = root.lastPingedTime_opaque;
2813 - if (
2814 - !isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) &&
2815 - !isSameOrHigherPriority(lastPingedTime, suspendedTime)
2816 - ) {
2817 - // There's already a lower priority ping scheduled.
2818 - return;
2819 - }
2820 -
2821 - // Mark the time at which this ping was scheduled.
2822 - root.lastPingedTime_opaque = suspendedTime;
2823 -
2561 ensureRootIsScheduled(root);
2825 - schedulePendingInteractions(root, suspendedTime);
2562 + schedulePendingInteractions(root, pingedLanes);
2563 }
2564
2828 -function retryTimedOutBoundary(
2829 - boundaryFiber: Fiber,
2830 - retryTime: ExpirationTimeOpaque,
2831 -) {
2565 +function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
2566 // The boundary fiber (a Suspense component or SuspenseList component)
2567 // previously was rendered in its fallback state. One of the promises that
2568 // suspended it has resolved, which means at least part of the tree was
2569 // likely unblocked. Try rendering again, at a new expiration time.
2836 - if (isSameExpirationTime(retryTime, (NoWork: ExpirationTimeOpaque))) {
2570 + if (retryLane === NoLane) {
2571 const suspenseConfig = null; // Retries don't carry over the already committed update.
2838 - retryTime = requestUpdateExpirationTime(boundaryFiber, suspenseConfig);
2572 + // TODO: Should retries get their own lane? Maybe it could share with
2573 + // transitions.
2574 + retryLane = requestUpdateLane(boundaryFiber, suspenseConfig);
2575 }
2576 // TODO: Special case idle priority?
2841 - const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
2577 + const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
2578 if (root !== null) {
2579 ensureRootIsScheduled(root);
2844 - schedulePendingInteractions(root, retryTime);
2580 + schedulePendingInteractions(root, retryLane);
2581 }
2582 }
2583
2584 export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
2585 const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2850 - let retryTime = NoWork;
2586 + let retryLane = NoLane;
2587 if (suspenseState !== null) {
2852 - retryTime = suspenseState.retryTime;
2588 + retryLane = suspenseState.retryLane;
2589 }
2854 - retryTimedOutBoundary(boundaryFiber, retryTime);
2590 + retryTimedOutBoundary(boundaryFiber, retryLane);
2591 }
2592
2593 export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2858 - let retryTime = NoWork; // Default
2594 + let retryLane = NoLane; // Default
2595 let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
2596 if (enableSuspenseServerRenderer) {
2597 switch (boundaryFiber.tag) {
@@ -2863,7 +2599,7 @@ export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2599 retryCache = boundaryFiber.stateNode;
2600 const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2601 if (suspenseState !== null) {
2866 - retryTime = suspenseState.retryTime;
2602 + retryLane = suspenseState.retryLane;
2603 }
2604 break;
2605 case SuspenseListComponent:
@@ -2886,7 +2622,7 @@ export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2622 retryCache.delete(wakeable);
2623 }
2624
2889 - retryTimedOutBoundary(boundaryFiber, retryTime);
2625 + retryTimedOutBoundary(boundaryFiber, retryLane);
2626 }
2627
2628 // Computes the next Just Noticeable Difference (JND) boundary.
@@ -3054,7 +2790,7 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2790 let beginWork;
2791 if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2792 const dummyFiber = null;
3057 - beginWork = (current, unitOfWork, expirationTime) => {
2793 + beginWork = (current, unitOfWork, lanes) => {
2794 // If a component throws an error, we replay it again in a synchronously
2795 // dispatched event, so that the debugger will treat it as an uncaught
2796 // error See ReactErrorUtils for more information.
@@ -3066,7 +2802,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2802 unitOfWork,
2803 );
2804 try {
3069 - return originalBeginWork(current, unitOfWork, expirationTime);
2805 + return originalBeginWork(current, unitOfWork, lanes);
2806 } catch (originalError) {
2807 if (
2808 originalError !== null &&
@@ -3102,7 +2838,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2838 null,
2839 current,
2840 unitOfWork,
3105 - expirationTime,
2841 + lanes,
2842 );
2843
2844 if (hasCaughtError()) {
@@ -3316,31 +3052,28 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
3052 }
3053 }
3054
3319 -function computeThreadID(
3320 - root: FiberRoot,
3321 - expirationTime: ExpirationTimeOpaque,
3322 -) {
3055 +function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
3056 // Interaction threads are unique per root and expiration time.
3057 // NOTE: Intentionally unsound cast. All that matters is that it's a number
3058 // and it represents a batch of work. Could make a helper function instead,
3059 // but meh this is fine for now.
3327 - return (expirationTime: any) * 1000 + root.interactionThreadID;
3060 + return (lane: any) * 1000 + root.interactionThreadID;
3061 }
3062
3330 -export function markSpawnedWork(expirationTime: ExpirationTimeOpaque) {
3063 +export function markSpawnedWork(lane: Lane | Lanes) {
3064 if (!enableSchedulerTracing) {
3065 return;
3066 }
3067 if (spawnedWorkDuringRender === null) {
3335 - spawnedWorkDuringRender = [expirationTime];
3068 + spawnedWorkDuringRender = [lane];
3069 } else {
3337 - spawnedWorkDuringRender.push(expirationTime);
3070 + spawnedWorkDuringRender.push(lane);
3071 }
3072 }
3073
3074 function scheduleInteractions(
3075 root: FiberRoot,
3343 - expirationTime: ExpirationTimeOpaque,
3076 + lane: Lane | Lanes,
3077 interactions: Set<Interaction>,
3078 ) {
3079 if (!enableSchedulerTracing) {
@@ -3349,7 +3082,7 @@ function scheduleInteractions(
3082
3083 if (interactions.size > 0) {
3084 const pendingInteractionMap = root.pendingInteractionMap_new;
3352 - const pendingInteractions = pendingInteractionMap.get(expirationTime);
3085 + const pendingInteractions = pendingInteractionMap.get(lane);
3086 if (pendingInteractions != null) {
3087 interactions.forEach(interaction => {
3088 if (!pendingInteractions.has(interaction)) {
@@ -3360,7 +3093,7 @@ function scheduleInteractions(
3093 pendingInteractions.add(interaction);
3094 });
3095 } else {
3363 - pendingInteractionMap.set(expirationTime, new Set(interactions));
3096 + pendingInteractionMap.set(lane, new Set(interactions));
3097
3098 // Update the pending async work count for the current interactions.
3099 interactions.forEach(interaction => {
@@ -3370,16 +3103,13 @@ function scheduleInteractions(
3103
3104 const subscriber = __subscriberRef.current;
3105 if (subscriber !== null) {
3373 - const threadID = computeThreadID(root, expirationTime);
3106 + const threadID = computeThreadID(root, lane);
3107 subscriber.onWorkScheduled(interactions, threadID);
3108 }
3109 }
3110 }
3111
3379 -function schedulePendingInteractions(
3380 - root: FiberRoot,
3381 - expirationTime: ExpirationTimeOpaque,
3382 -) {
3112 +function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
3113 // This is called when work is scheduled on a root.
3114 // It associates the current interactions with the newly-scheduled expiration.
3115 // They will be restored when that expiration is later committed.
@@ -3387,13 +3117,10 @@ function schedulePendingInteractions(
3117 return;
3118 }
3119
3390 - scheduleInteractions(root, expirationTime, __interactionsRef.current);
3120 + scheduleInteractions(root, lane, __interactionsRef.current);
3121 }
3122
3393 -function startWorkOnPendingInteractions(
3394 - root: FiberRoot,
3395 - expirationTime: ExpirationTimeOpaque,
3396 -) {
3123 +function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
3124 // This is called when new work is started on a root.
3125 if (!enableSchedulerTracing) {
3126 return;
@@ -3404,8 +3131,8 @@ function startWorkOnPendingInteractions(
3131 // work triggered during the render phase will be associated with it.
3132 const interactions: Set<Interaction> = new Set();
3133 root.pendingInteractionMap_new.forEach(
3407 - (scheduledInteractions, scheduledExpirationTime) => {
3408 - if (isSameOrHigherPriority(scheduledExpirationTime, expirationTime)) {
3134 + (scheduledInteractions, scheduledLane) => {
3135 + if (includesSomeLane(lanes, scheduledLane)) {
3136 scheduledInteractions.forEach(interaction =>
3137 interactions.add(interaction),
3138 );
@@ -3423,7 +3150,7 @@ function startWorkOnPendingInteractions(
3150 if (interactions.size > 0) {
3151 const subscriber = __subscriberRef.current;
3152 if (subscriber !== null) {
3426 - const threadID = computeThreadID(root, expirationTime);
3153 + const threadID = computeThreadID(root, lanes);
3154 try {
3155 subscriber.onWorkStarted(interactions, threadID);
3156 } catch (error) {
@@ -3436,19 +3163,20 @@ function startWorkOnPendingInteractions(
3163 }
3164 }
3165
3439 -function finishPendingInteractions(root, committedExpirationTime) {
3166 +function finishPendingInteractions(root, committedLanes) {
3167 if (!enableSchedulerTracing) {
3168 return;
3169 }
3170
3444 - const earliestRemainingTimeAfterCommit = root.firstPendingTime_opaque;
3171 + const remainingLanesAfterCommit = root.pendingLanes;
3172
3173 let subscriber;
3174
3175 try {
3176 subscriber = __subscriberRef.current;
3177 if (subscriber !== null && root.memoizedInteractions.size > 0) {
3451 - const threadID = computeThreadID(root, committedExpirationTime);
3178 + // FIXME: More than one lane can finish in a single commit.
3179 + const threadID = computeThreadID(root, committedLanes);
3180 subscriber.onWorkStopped(root.memoizedInteractions, threadID);
3181 }
3182 } catch (error) {
@@ -3461,36 +3189,29 @@ function finishPendingInteractions(root, committedExpirationTime) {
3189 // Unless the render was suspended or cascading work was scheduled,
3190 // In which case– leave pending interactions until the subsequent render.
3191 const pendingInteractionMap = root.pendingInteractionMap_new;
3464 - pendingInteractionMap.forEach(
3465 - (scheduledInteractions, scheduledExpirationTime) => {
3466 - // Only decrement the pending interaction count if we're done.
3467 - // If there's still work at the current priority,
3468 - // That indicates that we are waiting for suspense data.
3469 - if (
3470 - !isSameOrHigherPriority(
3471 - earliestRemainingTimeAfterCommit,
3472 - scheduledExpirationTime,
3473 - )
3474 - ) {
3475 - pendingInteractionMap.delete(scheduledExpirationTime);
3192 + pendingInteractionMap.forEach((scheduledInteractions, lane) => {
3193 + // Only decrement the pending interaction count if we're done.
3194 + // If there's still work at the current priority,
3195 + // That indicates that we are waiting for suspense data.
3196 + if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
3197 + pendingInteractionMap.delete(lane);
3198
3477 - scheduledInteractions.forEach(interaction => {
3478 - interaction.__count--;
3199 + scheduledInteractions.forEach(interaction => {
3200 + interaction.__count--;
3201
3480 - if (subscriber !== null && interaction.__count === 0) {
3481 - try {
3482 - subscriber.onInteractionScheduledWorkCompleted(interaction);
3483 - } catch (error) {
3484 - // If the subscriber throws, rethrow it in a separate task
3485 - scheduleCallback(ImmediateSchedulerPriority, () => {
3486 - throw error;
3487 - });
3488 - }
3202 + if (subscriber !== null && interaction.__count === 0) {
3203 + try {
3204 + subscriber.onInteractionScheduledWorkCompleted(interaction);
3205 + } catch (error) {
3206 + // If the subscriber throws, rethrow it in a separate task
3207 + scheduleCallback(ImmediateSchedulerPriority, () => {
3208 + throw error;
3209 + });
3210 }
3490 - });
3491 - }
3492 - },
3493 - );
3211 + }
3212 + });
3213 + }
3214 + });
3215 }
3216 }
3217
packages/react-reconciler/src/ReactInternalTypes.js
+14 -22
@@ -23,7 +23,7 @@ import type {WorkTag} from './ReactWorkTags';
23 import type {TypeOfMode} from './ReactTypeOfMode';
24 import type {SideEffectTag} from './ReactSideEffectTags';
25 import type {ExpirationTime} from './ReactFiberExpirationTime.old';
26 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
26 +import type {Lane, Lanes} from './ReactFiberLane';
27 import type {HookType} from './ReactFiberHooks.old';
28 import type {RootTag} from './ReactRootTags';
29 import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
@@ -52,7 +52,7 @@ export type Dependencies_old = {
52 };
53
54 export type Dependencies_new = {
55 - expirationTime: ExpirationTimeOpaque,
55 + lanes: Lanes,
56 firstContext: ContextDependency<mixed> | null,
57 responders: Map<
58 ReactEventResponder<any, any>,
@@ -148,16 +148,13 @@ export type Fiber = {|
148 firstEffect: Fiber | null,
149 lastEffect: Fiber | null,
150
151 - // Represents a time in the future by which this work should be completed.
152 - // Does not include work found in its subtree.
151 + // Only used by old reconciler
152 expirationTime: ExpirationTime,
154 -
155 - // This is used to quickly determine if a subtree has no pending changes.
153 childExpirationTime: ExpirationTime,
154
155 // Only used by new reconciler
159 - expirationTime_opaque: ExpirationTimeOpaque,
160 - childExpirationTime_opaque: ExpirationTimeOpaque,
156 + lanes: Lanes,
157 + childLanes: Lanes,
158
159 // This is a pooled version of a Fiber. Every fiber that gets updated will
160 // eventually have a pair. There are cases when we can clean up pairs to save
@@ -255,8 +252,7 @@ type BaseFiberRootProperties = {|
252
253 // Represents the next task that the root should work on, or the current one
254 // if it's already working.
258 - // TODO: In the new system, this will be a Lanes bitmask.
259 - callbackId: ExpirationTimeOpaque,
255 + callbackId: Lanes,
256 // Whether the currently scheduled task for this root is synchronous or
257 // batched/concurrent. We have to track this because Scheduler does not
258 // support synchronous tasks, so we put those on a separate queue. So you
@@ -269,17 +265,13 @@ type BaseFiberRootProperties = {|
265 // timestamp, in milliseconds.
266 expiresAt: number,
267
272 - // Same as corresponding fields in the old reconciler, but opaque. These will
273 - // become bitmasks.
274 - finishedExpirationTime_opaque: ExpirationTimeOpaque,
275 - firstPendingTime_opaque: ExpirationTimeOpaque,
276 - lastPendingTime_opaque: ExpirationTimeOpaque,
277 - firstSuspendedTime_opaque: ExpirationTimeOpaque,
278 - lastSuspendedTime_opaque: ExpirationTimeOpaque,
279 - nextKnownPendingLevel_opaque: ExpirationTimeOpaque,
280 - lastPingedTime_opaque: ExpirationTimeOpaque,
281 - lastExpiredTime_opaque: ExpirationTimeOpaque,
282 - mutableSourceLastPendingUpdateTime_opaque: ExpirationTimeOpaque,
268 + pendingLanes: Lanes,
269 + suspendedLanes: Lanes,
270 + pingedLanes: Lanes,
271 + expiredLanes: Lanes,
272 + mutableReadLanes: Lanes,
273 +
274 + finishedLanes: Lanes,
275 |};
276
277 // The following attributes are only used by interaction tracing builds.
@@ -289,7 +281,7 @@ type BaseFiberRootProperties = {|
281 type ProfilingOnlyFiberRootProperties = {|
282 interactionThreadID: number,
283 memoizedInteractions: Set<Interaction>,
292 - pendingInteractionMap_new: Map<ExpirationTimeOpaque, Set<Interaction>>,
284 + pendingInteractionMap_new: Map<Lane | Lanes, Set<Interaction>>,
285 pendingInteractionMap_old: Map<ExpirationTime, Set<Interaction>>,
286 |};
287
packages/react-reconciler/src/ReactMutableSource.new.js
-45
@@ -7,16 +7,9 @@
7 * @flow
8 */
9
10 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
11 -import type {FiberRoot} from './ReactInternalTypes';
10 import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
11
12 import {isPrimaryRenderer} from './ReactFiberHostConfig';
15 -import {
16 - NoWork,
17 - isSameOrHigherPriority,
18 - isSameExpirationTime,
19 -} from './ReactFiberExpirationTime.new';
13
14 // Work in progress version numbers only apply to a single render,
15 // and should be reset before starting a new render.
@@ -30,44 +23,6 @@ if (__DEV__) {
23 rendererSigil = {};
24 }
25
33 -export function clearPendingUpdates(
34 - root: FiberRoot,
35 - expirationTime: ExpirationTimeOpaque,
36 -): void {
37 - if (
38 - isSameOrHigherPriority(
39 - root.mutableSourceLastPendingUpdateTime_opaque,
40 - expirationTime,
41 - )
42 - ) {
43 - // All updates for this source have been processed.
44 - root.mutableSourceLastPendingUpdateTime_opaque = NoWork;
45 - }
46 -}
47 -
48 -export function getLastPendingExpirationTime(
49 - root: FiberRoot,
50 -): ExpirationTimeOpaque {
51 - return root.mutableSourceLastPendingUpdateTime_opaque;
52 -}
53 -
54 -export function setPendingExpirationTime(
55 - root: FiberRoot,
56 - expirationTime: ExpirationTimeOpaque,
57 -): void {
58 - const mutableSourceLastPendingUpdateTime =
59 - root.mutableSourceLastPendingUpdateTime_opaque;
60 - if (
61 - isSameExpirationTime(
62 - mutableSourceLastPendingUpdateTime,
63 - (NoWork: ExpirationTimeOpaque),
64 - ) ||
65 - !isSameOrHigherPriority(expirationTime, mutableSourceLastPendingUpdateTime)
66 - ) {
67 - root.mutableSourceLastPendingUpdateTime_opaque = expirationTime;
68 - }
69 -}
70 -
26 export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
27 if (isPrimaryRenderer) {
28 workInProgressPrimarySources.push(mutableSource);
packages/react-reconciler/src/ReactUpdateQueue.new.js
+21 -22
@@ -85,14 +85,10 @@
85 // resources, but the final state is always the same.
86
87 import type {Fiber} from './ReactInternalTypes';
88 -import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
88 +import type {Lanes, Lane} from './ReactFiberLane';
89 import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
90
91 -import {
92 - NoWork,
93 - Sync,
94 - isSameOrHigherPriority,
95 -} from './ReactFiberExpirationTime.new';
91 +import {NoLane, NoLanes, isSubsetOfLanes, mergeLanes} from './ReactFiberLane';
92 import {
93 enterDisallowedContextReadInDEV,
94 exitDisallowedContextReadInDEV,
@@ -104,7 +100,7 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags
100 import {StrictMode} from './ReactTypeOfMode';
101 import {
102 markRenderEventTimeAndConfig,
107 - markUnprocessedUpdateTime,
103 + markSkippedUpdateLanes,
104 } from './ReactFiberWorkLoop.new';
105
106 import invariant from 'shared/invariant';
@@ -115,7 +111,7 @@ export type Update<State> = {|
111 // TODO: Temporary field. Will remove this by storing a map of
112 // transition -> event time on the root.
113 eventTime: number,
118 - expirationTime: ExpirationTimeOpaque,
114 + lane: Lane,
115 suspenseConfig: null | SuspenseConfig,
116
117 tag: 0 | 1 | 2 | 3,
@@ -192,12 +188,12 @@ export function cloneUpdateQueue<State>(
188
189 export function createUpdate(
190 eventTime: number,
195 - expirationTime: ExpirationTimeOpaque,
191 + lane: Lane,
192 suspenseConfig: null | SuspenseConfig,
193 ): Update<*> {
194 const update: Update<*> = {
195 eventTime,
200 - expirationTime,
196 + lane,
197 suspenseConfig,
198
199 tag: UpdateState,
@@ -272,7 +268,7 @@ export function enqueueCapturedUpdate<State>(
268 do {
269 const clone: Update<State> = {
270 eventTime: update.eventTime,
275 - expirationTime: update.expirationTime,
271 + lane: update.lane,
272 suspenseConfig: update.suspenseConfig,
273
274 tag: update.tag,
@@ -410,7 +406,7 @@ export function processUpdateQueue<State>(
406 workInProgress: Fiber,
407 props: any,
408 instance: any,
413 - renderExpirationTime: ExpirationTimeOpaque,
409 + renderLanes: Lanes,
410 ): void {
411 // This is always non-null on a ClassComponent or HostRoot
412 const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
@@ -467,7 +463,9 @@ export function processUpdateQueue<State>(
463 if (firstBaseUpdate !== null) {
464 // Iterate through the list of updates to compute the result.
465 let newState = queue.baseState;
470 - let newExpirationTime = NoWork;
466 + // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
467 + // from the original lanes.
468 + let newLanes = NoLanes;
469
470 let newBaseState = null;
471 let newFirstBaseUpdate = null;
@@ -475,15 +473,15 @@ export function processUpdateQueue<State>(
473
474 let update = firstBaseUpdate;
475 do {
476 + const updateLane = update.lane;
477 const updateEventTime = update.eventTime;
479 - const updateExpirationTime = update.expirationTime;
480 - if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
478 + if (!isSubsetOfLanes(renderLanes, updateLane)) {
479 // Priority is insufficient. Skip this update. If this is the first
480 // skipped update, the previous update/state is the new base
481 // update/state.
482 const clone: Update<State> = {
483 eventTime: updateEventTime,
486 - expirationTime: updateExpirationTime,
484 + lane: updateLane,
485 suspenseConfig: update.suspenseConfig,
486
487 tag: update.tag,
@@ -499,16 +497,17 @@ export function processUpdateQueue<State>(
497 newLastBaseUpdate = newLastBaseUpdate.next = clone;
498 }
499 // Update the remaining priority in the queue.
502 - if (!isSameOrHigherPriority(newExpirationTime, updateExpirationTime)) {
503 - newExpirationTime = updateExpirationTime;
504 - }
500 + newLanes = mergeLanes(newLanes, updateLane);
501 } else {
502 // This update does have sufficient priority.
503
504 if (newLastBaseUpdate !== null) {
505 const clone: Update<State> = {
506 eventTime: updateEventTime,
511 - expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
507 + // This update is going to be committed so we never want uncommit
508 + // it. Using NoLane works because 0 is a subset of all bitmasks, so
509 + // this will never be skipped by the check above.
510 + lane: NoLane,
511 suspenseConfig: update.suspenseConfig,
512
513 tag: update.tag,
@@ -583,8 +582,8 @@ export function processUpdateQueue<State>(
582 // dealt with the props. Context in components that specify
583 // shouldComponentUpdate is tricky; but we'll have to account for
584 // that regardless.
586 - markUnprocessedUpdateTime(newExpirationTime);
587 - workInProgress.expirationTime_opaque = newExpirationTime;
585 + markSkippedUpdateLanes(newLanes);
586 + workInProgress.lanes = newLanes;
587 workInProgress.memoizedState = newState;
588 }
589
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+86 -40
@@ -170,22 +170,32 @@ describe('ReactIncrementalUpdates', () => {
170 // Now flush the remaining work. Even though e and f were already processed,
171 // they should be processed again, to ensure that the terminal state
172 // is deterministic.
173 - expect(Scheduler).toFlushAndYield([
174 - 'a',
175 - 'b',
176 - 'c',
177 -
178 - // e, f, and g are in a separate batch from a, b, and c because they
179 - // were scheduled in the middle of a render
180 - 'e',
181 - 'f',
182 - 'g',
183 -
184 - 'd',
185 - 'e',
186 - 'f',
187 - 'g',
188 - ]);
173 + expect(Scheduler).toFlushAndYield(
174 + gate(flags =>
175 + flags.new
176 + ? ['a', 'b', 'c', 'd', 'e', 'f', 'g']
177 + : [
178 + 'a',
179 + 'b',
180 + 'c',
181 +
182 + // The old reconciler has a quirk where `d` has slightly lower
183 + // priority than `g`, because it was scheduled in the middle of a
184 + // render. This is an implementation detail, but I've left the
185 + // test in this branch as-is since this was written so long ago.
186 + // This first render does not include d.
187 + 'e',
188 + 'f',
189 + 'g',
190 +
191 + // This second render does.
192 + 'd',
193 + 'e',
194 + 'f',
195 + 'g',
196 + ],
197 + ),
198 + );
199 expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]);
200 });
201
@@ -244,22 +254,32 @@ describe('ReactIncrementalUpdates', () => {
254 // Now flush the remaining work. Even though e and f were already processed,
255 // they should be processed again, to ensure that the terminal state
256 // is deterministic.
247 - expect(Scheduler).toFlushAndYield([
248 - 'a',
249 - 'b',
250 - 'c',
251 -
252 - // e, f, and g are in a separate batch from a, b, and c because they
253 - // were scheduled in the middle of a render
254 - 'e',
255 - 'f',
256 - 'g',
257 -
258 - 'd',
259 - 'e',
260 - 'f',
261 - 'g',
262 - ]);
257 + expect(Scheduler).toFlushAndYield(
258 + gate(flags =>
259 + flags.new
260 + ? ['a', 'b', 'c', 'd', 'e', 'f', 'g']
261 + : [
262 + 'a',
263 + 'b',
264 + 'c',
265 +
266 + // The old reconciler has a quirk where `d` has slightly lower
267 + // priority than `g`, because it was scheduled in the middle of a
268 + // render. This is an implementation detail, but I've left the
269 + // test in this branch as-is since this was written so long ago.
270 + // This first render does not include d.
271 + 'e',
272 + 'f',
273 + 'g',
274 +
275 + // This second render does.
276 + 'd',
277 + 'e',
278 + 'f',
279 + 'g',
280 + ],
281 + ),
282 + );
283 expect(ReactNoop.getChildren()).toEqual([span('fg')]);
284 });
285
@@ -348,7 +368,7 @@ describe('ReactIncrementalUpdates', () => {
368 expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
369 });
370
351 - it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
371 + it('updates triggered from inside a class setState updater', () => {
372 let instance;
373 class Foo extends React.Component {
374 state = {};
@@ -372,12 +392,26 @@ describe('ReactIncrementalUpdates', () => {
392 });
393
394 expect(() =>
375 - expect(Scheduler).toFlushAndYield([
376 - 'setState updater',
377 - // Update b is enqueued with the same priority as update a, so it should
378 - // be flushed in the same commit.
379 - 'render',
380 - ]),
395 + expect(Scheduler).toFlushAndYield(
396 + gate(flags =>
397 + flags.new
398 + ? [
399 + 'setState updater',
400 + // In the new reconciler, updates inside the render phase are
401 + // treated as if they came from an event, so the update gets
402 + // shifted to a subsequent render.
403 + 'render',
404 + 'render',
405 + ]
406 + : [
407 + 'setState updater',
408 + // In the old reconciler, updates in the render phase receive
409 + // the currently rendering expiration time, so the update
410 + // flushes immediately in the same render.
411 + 'render',
412 + ],
413 + ),
414 + ),
415 ).toErrorDev(
416 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
417 'from inside an update function. Update functions should be pure, ' +
@@ -391,7 +425,19 @@ describe('ReactIncrementalUpdates', () => {
425 this.setState({a: 'a'});
426 return {b: 'b'};
427 });
394 - expect(Scheduler).toFlushAndYield(['render']);
428 + expect(Scheduler).toFlushAndYield(
429 + gate(flags =>
430 + flags.new
431 + ? // In the new reconciler, updates inside the render phase are
432 + // treated as if they came from an event, so the update gets shifted
433 + // to a subsequent render.
434 + ['render', 'render']
435 + : // In the old reconciler, updates in the render phase receive
436 + // the currently rendering expiration time, so the update flushes
437 + // immediately in the same render.
438 + ['render'],
439 + ),
440 + );
441 });
442
443 it('getDerivedStateFromProps should update base state of updateQueue (based on product bug)', () => {
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+18 -7
@@ -322,6 +322,7 @@ describe('ReactSuspense', () => {
322 },
323 );
324
325 + // @gate experimental
326 it(
327 'interrupts current render when something suspends with a ' +
328 "delay and we've already skipped over a lower priority update in " +
@@ -360,9 +361,14 @@ describe('ReactSuspense', () => {
361 // Do a bit of work
362 expect(Scheduler).toFlushAndYieldThrough(['A1']);
363
363 - // Schedule another update. This will get bumped into a different batch
364 - // because we're already in the middle of rendering.
365 - root.update(<App shouldSuspend={false} step={2} />);
364 + // Schedule another update. This will have lower priority because it's
365 + // a transition.
366 + React.unstable_withSuspenseConfig(
367 + () => {
368 + root.update(<App shouldSuspend={false} step={2} />);
369 + },
370 + {timeoutMs: 10000},
371 + );
372
373 // Interrupt to trigger a restart.
374 interrupt();
@@ -389,6 +395,7 @@ describe('ReactSuspense', () => {
395 },
396 );
397
398 + // @gate experimental
399 it(
400 'interrupts current render when something suspends with a ' +
401 "delay and we've already bailed out lower priority update in " +
@@ -450,16 +457,20 @@ describe('ReactSuspense', () => {
457 setShouldSuspend(true);
458
459 // Need to move into the next async bucket.
453 - Scheduler.unstable_advanceTime(1000);
460 // Do a bit of work, then interrupt to trigger a restart.
461 expect(Scheduler).toFlushAndYieldThrough(['A']);
462 interrupt();
463 // Should not have committed loading state
464 expect(root).toMatchRenderedOutput('ABC');
465
460 - // Schedule another update. This will have lower priority because of
461 - // the interrupt trick above.
462 - setShouldHideInParent(true);
466 + // Schedule another update. This will have lower priority because it's
467 + // a transition.
468 + React.unstable_withSuspenseConfig(
469 + () => {
470 + setShouldHideInParent(true);
471 + },
472 + {timeoutMs: 10000},
473 + );
474
475 expect(Scheduler).toFlushAndYieldThrough([
476 // Should have restarted the first update, because of the interruption
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.internal.js
+21 -24
@@ -31,23 +31,20 @@ describe('ReactSuspense', () => {
31
32 function createThenable() {
33 let completed = false;
34 - const resolveRef = {current: null};
35 - const promise = {
36 - then(resolve, reject) {
37 - resolveRef.current = () => {
38 - completed = true;
39 - resolve();
40 - };
41 - },
42 - };
43 -
34 + let resolve;
35 + const promise = new Promise(res => {
36 + resolve = () => {
37 + completed = true;
38 + res();
39 + };
40 + });
41 const PromiseComp = () => {
42 if (!completed) {
43 throw promise;
44 }
45 return 'Done';
46 };
50 - return {promise, resolveRef, PromiseComp};
47 + return {promise, resolve, PromiseComp};
48 }
49
50 it('check type', () => {
@@ -74,8 +71,8 @@ describe('ReactSuspense', () => {
71 expect(() => Scheduler.unstable_flushAll()).toErrorDev([]);
72 });
73
77 - it('1 then 0 suspense callback', () => {
78 - const {promise, resolveRef, PromiseComp} = createThenable();
74 + it('1 then 0 suspense callback', async () => {
75 + const {promise, resolve, PromiseComp} = createThenable();
76
77 let ops = [];
78 const suspenseCallback = thenables => {
@@ -94,21 +91,21 @@ describe('ReactSuspense', () => {
91 expect(ops).toEqual([new Set([promise])]);
92 ops = [];
93
97 - resolveRef.current();
94 + await resolve();
95 expect(Scheduler).toFlushWithoutYielding();
96 expect(ReactNoop.getChildren()).toEqual([text('Done')]);
97 expect(ops).toEqual([]);
98 });
99
103 - it('2 then 1 then 0 suspense callback', () => {
100 + it('2 then 1 then 0 suspense callback', async () => {
101 const {
102 promise: promise1,
106 - resolveRef: resolveRef1,
103 + resolve: resolve1,
104 PromiseComp: PromiseComp1,
105 } = createThenable();
106 const {
107 promise: promise2,
111 - resolveRef: resolveRef2,
108 + resolve: resolve2,
109 PromiseComp: PromiseComp2,
110 } = createThenable();
111
@@ -132,14 +129,14 @@ describe('ReactSuspense', () => {
129 expect(ops).toEqual([new Set([promise1, promise2])]);
130 ops = [];
131
135 - resolveRef1.current();
132 + await resolve1();
133 ReactNoop.render(element);
134 expect(Scheduler).toFlushWithoutYielding();
135 expect(ReactNoop.getChildren()).toEqual([text('Waiting Tier 1')]);
136 expect(ops).toEqual([new Set([promise2])]);
137 ops = [];
138
142 - resolveRef2.current();
139 + await resolve2();
140 ReactNoop.render(element);
141 expect(Scheduler).toFlushWithoutYielding();
142 expect(ReactNoop.getChildren()).toEqual([text('Done'), text('Done')]);
@@ -177,15 +174,15 @@ describe('ReactSuspense', () => {
174 expect(ops2).toEqual([new Set([promise])]);
175 });
176
180 - it('competing suspense promises', () => {
177 + it('competing suspense promises', async () => {
178 const {
179 promise: promise1,
183 - resolveRef: resolveRef1,
180 + resolve: resolve1,
181 PromiseComp: PromiseComp1,
182 } = createThenable();
183 const {
184 promise: promise2,
188 - resolveRef: resolveRef2,
185 + resolve: resolve2,
186 PromiseComp: PromiseComp2,
187 } = createThenable();
188
@@ -219,7 +216,7 @@ describe('ReactSuspense', () => {
216 ops1 = [];
217 ops2 = [];
218
222 - resolveRef1.current();
219 + await resolve1();
220 ReactNoop.render(element);
221 expect(Scheduler).toFlushWithoutYielding();
222 expect(ReactNoop.getChildren()).toEqual([
@@ -231,7 +228,7 @@ describe('ReactSuspense', () => {
228 ops1 = [];
229 ops2 = [];
230
234 - resolveRef2.current();
231 + await resolve2();
232 ReactNoop.render(element);
233 expect(Scheduler).toFlushWithoutYielding();
234 expect(ReactNoop.getChildren()).toEqual([text('Done'), text('Done')]);
scripts/error-codes/codes.json
+4 -1
@@ -354,5 +354,8 @@
354 "354": "getInspectorDataForViewAtPoint() is not available in production.",
355 "355": "The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. Do not read the value directly.",
356 "356": "Could not read the cache.",
357 - "357": "The current renderer does not support React Scopes. This error is likely caused by a bug in React. Please file an issue."
357 + "357": "The current renderer does not support React Scopes. This error is likely caused by a bug in React. Please file an issue.",
358 + "358": "Invalid update priority: %s. This is a bug in React.",
359 + "359": "Invalid transition priority: %s. This is a bug in React.",
360 + "360": "Invalid lane: %s. This is a bug in React."
361 }