@@ -29,6 +29,7 @@ export opaque type LanePriority =
29
29
| 16 ;
30
30
export opaque type Lanes = number ;
31
31
export opaque type Lane = number ;
32
+ export opaque type LaneMap < T > = Array < T > ;
32
33
33
34
import invariant from 'shared/invariant' ;
34
35
@@ -66,7 +67,7 @@ const IdleLanePriority: LanePriority = 2;
66
67
67
68
const OffscreenLanePriority : LanePriority = 1 ;
68
69
69
- const NoLanePriority : LanePriority = 0 ;
70
+ export const NoLanePriority : LanePriority = 0 ;
70
71
71
72
const TotalLanes = 31 ;
72
73
@@ -117,6 +118,8 @@ const IdleUpdateRangeEnd = 30;
117
118
118
119
export const OffscreenLane : Lane = /* */ 0b1000000000000000000000000000000 ;
119
120
121
+ export const NoTimestamp = - 1 ;
122
+
120
123
// "Registers" used to "return" multiple values
121
124
// Used by getHighestPriorityLanes and getNextLanes:
122
125
let return_highestLanePriority : LanePriority = DefaultLanePriority ;
@@ -365,6 +368,63 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
365
368
return nextLanes ;
366
369
}
367
370
371
+ function computeExpirationTime ( lane : Lane , currentTime : number ) {
372
+ // TODO: Expiration heuristic is constant per lane, so could use a map.
373
+ getHighestPriorityLanes ( lane ) ;
374
+ const priority = return_highestLanePriority ;
375
+ if ( priority >= InputContinuousLanePriority ) {
376
+ // User interactions should expire slightly more quickly.
377
+ return currentTime + 1000 ;
378
+ } else if ( priority >= TransitionLongLanePriority ) {
379
+ return currentTime + 5000 ;
380
+ } else {
381
+ // Anything idle priority or lower should never expire.
382
+ return NoTimestamp ;
383
+ }
384
+ }
385
+
386
+ export function markStarvedLanesAsExpired (
387
+ root : FiberRoot ,
388
+ currentTime : number ,
389
+ ) : void {
390
+ // TODO: This gets called every time we yield. We can optimize by storing
391
+ // the earliest expiration time on the root. Then use that to quickly bail out
392
+ // of this function.
393
+
394
+ const pendingLanes = root . pendingLanes ;
395
+ const suspendedLanes = root . suspendedLanes ;
396
+ const pingedLanes = root . pingedLanes ;
397
+ const expirationTimes = root . expirationTimes ;
398
+
399
+ // Iterate through the pending lanes and check if we've reached their
400
+ // expiration time. If so, we'll assume the update is being starved and mark
401
+ // it as expired to force it to finish.
402
+ let lanes = pendingLanes ;
403
+ while ( lanes > 0 ) {
404
+ const index = ctrz ( lanes ) ;
405
+ const lane = 1 << index ;
406
+
407
+ const expirationTime = expirationTimes [ index ] ;
408
+ if ( expirationTime === NoTimestamp ) {
409
+ // Found a pending lane with no expiration time. If it's not suspended, or
410
+ // if it's pinged, assume it's CPU-bound. Compute a new expiration time
411
+ // using the current time.
412
+ if (
413
+ ( lane & suspendedLanes ) === NoLanes ||
414
+ ( lane & pingedLanes ) !== NoLanes
415
+ ) {
416
+ // Assumes timestamps are monotonically increasing.
417
+ expirationTimes [ index ] = computeExpirationTime ( lane , currentTime ) ;
418
+ }
419
+ } else if ( expirationTime <= currentTime ) {
420
+ // This lane expired
421
+ root . expiredLanes |= lane ;
422
+ }
423
+
424
+ lanes &= ~ lane ;
425
+ }
426
+ }
427
+
368
428
// This returns the highest priority pending lanes regardless of whether they
369
429
// are suspended.
370
430
export function getHighestPriorityPendingLanes ( root : FiberRoot ) {
@@ -555,6 +615,10 @@ export function higherPriorityLane(a: Lane, b: Lane) {
555
615
return a !== NoLane && a < b ? a : b ;
556
616
}
557
617
618
+ export function createLaneMap < T > (initial: T): LaneMap< T > {
619
+ return new Array ( TotalLanes ) . fill ( initial ) ;
620
+ }
621
+
558
622
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
559
623
root . pendingLanes |= updateLane ;
560
624
@@ -570,16 +634,33 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
570
634
571
635
// Unsuspend any update at equal or lower priority.
572
636
const higherPriorityLanes = updateLane - 1 ; // Turns 0b1000 into 0b0111
637
+
573
638
root . suspendedLanes &= higherPriorityLanes ;
574
639
root . pingedLanes &= higherPriorityLanes ;
575
640
}
576
641
577
642
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
578
643
root . suspendedLanes |= suspendedLanes ;
579
644
root . pingedLanes &= ~ suspendedLanes ;
645
+
646
+ // The suspended lanes are no longer CPU-bound. Clear their expiration times.
647
+ const expirationTimes = root . expirationTimes ;
648
+ let lanes = suspendedLanes ;
649
+ while ( lanes > 0 ) {
650
+ const index = ctrz ( lanes ) ;
651
+ const lane = 1 << index ;
652
+
653
+ expirationTimes [ index ] = NoTimestamp ;
654
+
655
+ lanes &= ~ lane ;
656
+ }
580
657
}
581
658
582
- export function markRootPinged ( root : FiberRoot , pingedLanes : Lanes ) {
659
+ export function markRootPinged(
660
+ root: FiberRoot,
661
+ pingedLanes: Lanes,
662
+ eventTime: number,
663
+ ) {
583
664
root . pingedLanes |= root . suspendedLanes & pingedLanes ;
584
665
}
585
666
@@ -600,6 +681,8 @@ export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
600
681
}
601
682
602
683
export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
684
+ const noLongerPendingLanes = root . pendingLanes & ~ remainingLanes ;
685
+
603
686
root . pendingLanes = remainingLanes ;
604
687
605
688
// Let's try everything again
@@ -608,6 +691,18 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
608
691
609
692
root . expiredLanes &= remainingLanes ;
610
693
root . mutableReadLanes &= remainingLanes ;
694
+
695
+ const expirationTimes = root . expirationTimes ;
696
+ let lanes = noLongerPendingLanes ;
697
+ while ( lanes > 0 ) {
698
+ const index = ctrz ( lanes ) ;
699
+ const lane = 1 << index ;
700
+
701
+ // Clear the expiration time
702
+ expirationTimes [ index ] = - 1 ;
703
+
704
+ lanes &= ~ lane ;
705
+ }
611
706
}
612
707
613
708
export function getBumpedLaneForHydration(
@@ -671,18 +766,25 @@ export function getBumpedLaneForHydration(
671
766
672
767
const clz32 = Math . clz32 ? Math . clz32 : clz32Fallback ;
673
768
674
- // Taken from:
769
+ // Count leading zeros. Only used on lanes, so assume input is an integer.
770
+ // Based on:
675
771
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
676
772
const log = Math . log ;
677
773
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 ) {
774
+ function clz32Fallback ( lanes : Lanes | Lane ) {
775
+ if ( lanes === 0 ) {
685
776
return 32 ;
686
777
}
687
- return ( 31 - ( ( log ( asUint ) / LN2 ) | 0 ) ) | 0 ; // the "| 0" acts like math.floor
778
+ return (31 - ((log(lanes) / LN2) | 0)) | 0;
779
+ }
780
+
781
+ // Count trailing zeros. Only used on lanes, so assume input is an integer.
782
+ function ctrz ( lanes : Lanes | Lane ) {
783
+ let bits = lanes ;
784
+ bits |= bits << 16 ;
785
+ bits |= bits << 8 ;
786
+ bits |= bits << 4 ;
787
+ bits |= bits << 2 ;
788
+ bits |= bits << 1 ;
789
+ return 32 - clz32 ( ~ bits ) ;
688
790
}
0 commit comments