@@ -36,10 +36,7 @@ export type Lane = number;
3636export type LaneMap < T > = Array < T > ;
3737
3838import invariant from 'shared/invariant' ;
39- import {
40- enableCache ,
41- enableTransitionEntanglement ,
42- } from 'shared/ReactFeatureFlags' ;
39+ import { enableCache } from 'shared/ReactFeatureFlags' ;
4340
4441import {
4542 ImmediatePriority as ImmediateSchedulerPriority ,
@@ -95,6 +92,7 @@ export const DefaultLanes: Lanes = /* */ 0b0000000000000000000
9592
9693const TransitionHydrationLane : Lane = /* */ 0b0000000000000000001000000000000 ;
9794const TransitionLanes : Lanes = /* */ 0b0000000001111111110000000000000 ;
95+ const SomeTransitionLane : Lane = /* */ 0b0000000000000000010000000000000 ;
9896
9997const RetryLanes : Lanes = /* */ 0b0000011110000000000000000000000 ;
10098
@@ -113,6 +111,9 @@ export const NoTimestamp = -1;
113111
114112let currentUpdateLanePriority : LanePriority = NoLanePriority ;
115113
114+ let nextTransitionLane : Lane = SomeTransitionLane ;
115+ let nextRetryLane : Lane = SomeRetryLane ;
116+
116117export function getCurrentUpdateLanePriority ( ) : LanePriority {
117118 return currentUpdateLanePriority ;
118119}
@@ -309,15 +310,6 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
309310 return NoLanes ;
310311 }
311312
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-
321313 // If we're already in the middle of a render, switching lanes will interrupt
322314 // it and we'll lose our progress. We should only do this if the new lanes are
323315 // higher priority.
@@ -350,6 +342,11 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
350342 // entanglement is usually "best effort": we'll try our best to render the
351343 // lanes in the same batch, but it's not worth throwing out partially
352344 // 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.
353350 //
354351 // For those exceptions where entanglement is semantically important, like
355352 // useMutableSource, we should ensure that there is no partial work at the
@@ -559,34 +556,23 @@ export function findUpdateLane(
559556 ) ;
560557}
561558
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 ;
577567 }
578568 return lane ;
579569}
580570
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 ;
590576 }
591577 return lane ;
592578}
@@ -595,16 +581,6 @@ function getHighestPriorityLane(lanes: Lanes) {
595581 return lanes & - lanes ;
596582}
597583
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-
608584export function pickArbitraryLane ( lanes : Lanes ) : Lane {
609585 // This wrapper function gets inlined. Only exists so to communicate that it
610586 // doesn't matter which bit is selected; you can pick any bit without
@@ -676,39 +652,21 @@ export function markRootUpdated(
676652) {
677653 root . pendingLanes |= updateLane ;
678654
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 ;
712670 }
713671
714672 const eventTimes = root . eventTimes ;
@@ -801,16 +759,32 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
801759}
802760
803761export 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.
805773
774+ const rootEntangledLanes = ( root . entangledLanes |= entangledLanes ) ;
806775 const entanglements = root . entanglements ;
807- let lanes = entangledLanes ;
808- while ( lanes > 0 ) {
776+ let lanes = rootEntangledLanes ;
777+ while ( lanes ) {
809778 const index = pickArbitraryLaneIndex ( lanes ) ;
810779 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+ }
814788 lanes &= ~ lane ;
815789 }
816790}
0 commit comments