@@ -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