@@ -36,10 +36,7 @@ export type Lane = number;
36
36
export type LaneMap < T > = Array < T > ;
37
37
38
38
import invariant from 'shared/invariant' ;
39
- import {
40
- enableCache ,
41
- enableTransitionEntanglement ,
42
- } from 'shared/ReactFeatureFlags' ;
39
+ import { enableCache } from 'shared/ReactFeatureFlags' ;
43
40
44
41
import {
45
42
ImmediatePriority as ImmediateSchedulerPriority ,
@@ -95,6 +92,7 @@ export const DefaultLanes: Lanes = /* */ 0b0000000000000000000
95
92
96
93
const TransitionHydrationLane : Lane = /* */ 0b0000000000000000001000000000000 ;
97
94
const TransitionLanes : Lanes = /* */ 0b0000000001111111110000000000000 ;
95
+ const SomeTransitionLane : Lane = /* */ 0b0000000000000000010000000000000 ;
98
96
99
97
const RetryLanes : Lanes = /* */ 0b0000011110000000000000000000000 ;
100
98
@@ -113,6 +111,9 @@ export const NoTimestamp = -1;
113
111
114
112
let currentUpdateLanePriority : LanePriority = NoLanePriority ;
115
113
114
+ let nextTransitionLane : Lane = SomeTransitionLane ;
115
+ let nextRetryLane : Lane = SomeRetryLane ;
116
+
116
117
export function getCurrentUpdateLanePriority ( ) : LanePriority {
117
118
return currentUpdateLanePriority ;
118
119
}
@@ -309,15 +310,6 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
309
310
return NoLanes ;
310
311
}
311
312
312
- if ( enableTransitionEntanglement ) {
313
- // We don't need to do anything extra here, because we apply per-lane
314
- // transition entanglement in the entanglement loop below.
315
- } else {
316
- // If there are higher priority lanes, we'll include them even if they
317
- // are suspended.
318
- nextLanes = pendingLanes & getEqualOrHigherPriorityLanes ( nextLanes ) ;
319
- }
320
-
321
313
// If we're already in the middle of a render, switching lanes will interrupt
322
314
// it and we'll lose our progress. We should only do this if the new lanes are
323
315
// higher priority.
@@ -350,6 +342,11 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
350
342
// entanglement is usually "best effort": we'll try our best to render the
351
343
// lanes in the same batch, but it's not worth throwing out partially
352
344
// completed work in order to do it.
345
+ // TODO: Reconsider this. The counter-argument is that the partial work
346
+ // represents an intermediate state, which we don't want to show to the user.
347
+ // And by spending extra time finishing it, we're increasing the amount of
348
+ // time it takes to show the final state, which is what they are actually
349
+ // waiting for.
353
350
//
354
351
// For those exceptions where entanglement is semantically important, like
355
352
// useMutableSource, we should ensure that there is no partial work at the
@@ -559,34 +556,23 @@ export function findUpdateLane(
559
556
) ;
560
557
}
561
558
562
- // To ensure consistency across multiple updates in the same event, this should
563
- // be pure function, so that it always returns the same lane for given inputs.
564
- export function findTransitionLane ( wipLanes : Lanes , pendingLanes : Lanes ) : Lane {
565
- // First look for lanes that are completely unclaimed, i.e. have no
566
- // pending work.
567
- let lane = pickArbitraryLane ( TransitionLanes & ~ pendingLanes ) ;
568
- if ( lane === NoLane ) {
569
- // If all lanes have pending work, look for a lane that isn't currently
570
- // being worked on.
571
- lane = pickArbitraryLane ( TransitionLanes & ~ wipLanes ) ;
572
- if ( lane === NoLane ) {
573
- // If everything is being worked on, pick any lane. This has the
574
- // effect of interrupting the current work-in-progress.
575
- lane = pickArbitraryLane ( TransitionLanes ) ;
576
- }
559
+ export function claimNextTransitionLane ( ) : Lane {
560
+ // Cycle through the lanes, assigning each new transition to the next lane.
561
+ // In most cases, this means every transition gets its own lane, until we
562
+ // run out of lanes and cycle back to the beginning.
563
+ const lane = nextTransitionLane ;
564
+ nextTransitionLane <<= 1 ;
565
+ if ( ( nextTransitionLane & TransitionLanes ) === 0 ) {
566
+ nextTransitionLane = SomeTransitionLane ;
577
567
}
578
568
return lane ;
579
569
}
580
570
581
- // To ensure consistency across multiple updates in the same event, this should
582
- // be pure function, so that it always returns the same lane for given inputs.
583
- export function findRetryLane ( wipLanes : Lanes ) : Lane {
584
- // This is a fork of `findUpdateLane` designed specifically for Suspense
585
- // "retries" — a special update that attempts to flip a Suspense boundary
586
- // from its placeholder state to its primary/resolved state.
587
- let lane = pickArbitraryLane ( RetryLanes & ~ wipLanes ) ;
588
- if ( lane === NoLane ) {
589
- lane = pickArbitraryLane ( RetryLanes ) ;
571
+ export function claimNextRetryLane ( ) : Lane {
572
+ const lane = nextRetryLane ;
573
+ nextRetryLane <<= 1 ;
574
+ if ( ( nextRetryLane & RetryLanes ) === 0 ) {
575
+ nextRetryLane = SomeRetryLane ;
590
576
}
591
577
return lane ;
592
578
}
@@ -595,16 +581,6 @@ function getHighestPriorityLane(lanes: Lanes) {
595
581
return lanes & - lanes ;
596
582
}
597
583
598
- function getLowestPriorityLane ( lanes : Lanes ) : Lane {
599
- // This finds the most significant non-zero bit.
600
- const index = 31 - clz32 ( lanes ) ;
601
- return index < 0 ? NoLanes : 1 << index ;
602
- }
603
-
604
- function getEqualOrHigherPriorityLanes ( lanes : Lanes | Lane ) : Lanes {
605
- return ( getLowestPriorityLane ( lanes ) << 1 ) - 1 ;
606
- }
607
-
608
584
export function pickArbitraryLane ( lanes : Lanes ) : Lane {
609
585
// This wrapper function gets inlined. Only exists so to communicate that it
610
586
// doesn't matter which bit is selected; you can pick any bit without
@@ -676,39 +652,21 @@ export function markRootUpdated(
676
652
) {
677
653
root . pendingLanes |= updateLane ;
678
654
679
- // TODO: Theoretically, any update to any lane can unblock any other lane. But
680
- // it's not practical to try every single possible combination. We need a
681
- // heuristic to decide which lanes to attempt to render, and in which batches.
682
- // For now, we use the same heuristic as in the old ExpirationTimes model:
683
- // retry any lane at equal or lower priority, but don't try updates at higher
684
- // priority without also including the lower priority updates. This works well
685
- // when considering updates across different priority levels, but isn't
686
- // sufficient for updates within the same priority, since we want to treat
687
- // those updates as parallel.
688
-
689
- // Unsuspend any update at equal or lower priority.
690
- const higherPriorityLanes = updateLane - 1 ; // Turns 0b1000 into 0b0111
691
-
692
- if ( enableTransitionEntanglement ) {
693
- // If there are any suspended transitions, it's possible this new update
694
- // could unblock them. Clear the suspended lanes so that we can try rendering
695
- // them again.
696
- //
697
- // TODO: We really only need to unsuspend only lanes that are in the
698
- // `subtreeLanes` of the updated fiber, or the update lanes of the return
699
- // path. This would exclude suspended updates in an unrelated sibling tree,
700
- // since there's no way for this update to unblock it.
701
- //
702
- // We don't do this if the incoming update is idle, because we never process
703
- // idle updates until after all the regular updates have finished; there's no
704
- // way it could unblock a transition.
705
- if ( ( updateLane & IdleLanes ) === NoLanes ) {
706
- root . suspendedLanes = NoLanes ;
707
- root . pingedLanes = NoLanes ;
708
- }
709
- } else {
710
- root . suspendedLanes &= higherPriorityLanes ;
711
- root . pingedLanes &= higherPriorityLanes ;
655
+ // If there are any suspended transitions, it's possible this new update
656
+ // could unblock them. Clear the suspended lanes so that we can try rendering
657
+ // them again.
658
+ //
659
+ // TODO: We really only need to unsuspend only lanes that are in the
660
+ // `subtreeLanes` of the updated fiber, or the update lanes of the return
661
+ // path. This would exclude suspended updates in an unrelated sibling tree,
662
+ // since there's no way for this update to unblock it.
663
+ //
664
+ // We don't do this if the incoming update is idle, because we never process
665
+ // idle updates until after all the regular updates have finished; there's no
666
+ // way it could unblock a transition.
667
+ if ( ( updateLane & IdleLanes ) === NoLanes ) {
668
+ root . suspendedLanes = NoLanes ;
669
+ root . pingedLanes = NoLanes ;
712
670
}
713
671
714
672
const eventTimes = root . eventTimes ;
@@ -801,16 +759,32 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
801
759
}
802
760
803
761
export function markRootEntangled ( root : FiberRoot , entangledLanes : Lanes ) {
804
- root . entangledLanes |= entangledLanes ;
762
+ // In addition to entangling each of the given lanes with each other, we also
763
+ // have to consider _transitive_ entanglements. For each lane that is already
764
+ // entangled with *any* of the given lanes, that lane is now transitively
765
+ // entangled with *all* the given lanes.
766
+ //
767
+ // Translated: If C is entangled with A, then entangling A with B also
768
+ // entangles C with B.
769
+ //
770
+ // If this is hard to grasp, it might help to intentionally break this
771
+ // function and look at the tests that fail in ReactTransition-test.js. Try
772
+ // commenting out one of the conditions below.
805
773
774
+ const rootEntangledLanes = ( root . entangledLanes |= entangledLanes ) ;
806
775
const entanglements = root . entanglements ;
807
- let lanes = entangledLanes ;
808
- while ( lanes > 0 ) {
776
+ let lanes = rootEntangledLanes ;
777
+ while ( lanes ) {
809
778
const index = pickArbitraryLaneIndex ( lanes ) ;
810
779
const lane = 1 << index ;
811
-
812
- entanglements [ index ] |= entangledLanes ;
813
-
780
+ if (
781
+ // Is this one of the newly entangled lanes?
782
+ ( lane & entangledLanes ) |
783
+ // Is this lane transitively entangled with the newly entangled lanes?
784
+ ( entanglements [ index ] & entangledLanes )
785
+ ) {
786
+ entanglements [ index ] |= entangledLanes ;
787
+ }
814
788
lanes &= ~ lane ;
815
789
}
816
790
}
0 commit comments