Track event times per lane on the root (#19387)
* Pass event time to markRootUpdated Some minor rearranging so that eventTime gets threaded through. No change in behavior. * Track event times per lane on the root Previous strategy was to store the event time on the update object and accumulate the most recent one during the render phase. Among other advantages, by tracking them on the root, we can read the event time before the render phase has finished. I haven't removed the `eventTime` field from the update object yet, because it's still used to compute the timeout. Tracking the timeout on the root is my next step.
Andrew Clark committed
Jul 17, 2020 at 12:47 UTC
b23ea02be507cc08747d49c1994f283514c5aeea
6 files changed
+166
-141
packages/react-reconciler/src/ReactFiberLane.js
+41
-3
@@ -374,6 +374,25 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
374
return nextLanes;
375
}
376
377
+export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
378
+ const eventTimes = root.eventTimes;
379
+
380
+ let mostRecentEventTime = NoTimestamp;
381
+ while (lanes > 0) {
382
+ const index = pickArbitraryLaneIndex(lanes);
383
+ const lane = 1 << index;
384
+
385
+ const eventTime = eventTimes[index];
386
+ if (eventTime > mostRecentEventTime) {
387
+ mostRecentEventTime = eventTime;
388
+ }
389
+
390
+ lanes &= ~lane;
391
+ }
392
+
393
+ return mostRecentEventTime;
394
+}
395
+
396
function computeExpirationTime(lane: Lane, currentTime: number) {
397
// TODO: Expiration heuristic is constant per lane, so could use a map.
398
getHighestPriorityLanes(lane);
@@ -606,10 +625,14 @@ export function pickArbitraryLane(lanes: Lanes): Lane {
625
return getHighestPriorityLane(lanes);
626
}
627
609
-function pickArbitraryLaneIndex(lanes: Lane | Lanes) {
628
+function pickArbitraryLaneIndex(lanes: Lanes) {
629
return 31 - clz32(lanes);
630
}
631
632
+function laneToIndex(lane: Lane) {
633
+ return pickArbitraryLaneIndex(lane);
634
+}
635
+
636
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
637
return (a & b) !== NoLanes;
638
}
@@ -648,7 +671,11 @@ export function createLaneMap<T>(initial: T): LaneMap<T> {
671
return new Array(TotalLanes).fill(initial);
672
}
673
651
-export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
674
+export function markRootUpdated(
675
+ root: FiberRoot,
676
+ updateLane: Lane,
677
+ eventTime: number,
678
+) {
679
root.pendingLanes |= updateLane;
680
681
// TODO: Theoretically, any update to any lane can unblock any other lane. But
@@ -666,6 +693,12 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
693
694
root.suspendedLanes &= higherPriorityLanes;
695
root.pingedLanes &= higherPriorityLanes;
696
+
697
+ const eventTimes = root.eventTimes;
698
+ const index = laneToIndex(updateLane);
699
+ // We can always overwrite an existing timestamp because we prefer the most
700
+ // recent event, and we assume time is monotonically increasing.
701
+ eventTimes[index] = eventTime;
702
}
703
704
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
@@ -723,13 +756,18 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
756
757
root.entangledLanes &= remainingLanes;
758
759
+ const entanglements = root.entanglements;
760
+ const eventTimes = root.eventTimes;
761
const expirationTimes = root.expirationTimes;
762
+
763
+ // Clear the lanes that no longer have pending work
764
let lanes = noLongerPendingLanes;
765
while (lanes > 0) {
766
const index = pickArbitraryLaneIndex(lanes);
767
const lane = 1 << index;
768
732
- // Clear the expiration time
769
+ entanglements[index] = NoLanes;
770
+ eventTimes[index] = NoTimestamp;
771
expirationTimes[index] = NoTimestamp;
772
773
lanes &= ~lane;
packages/react-reconciler/src/ReactFiberRoot.new.js
+1
@@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) {
40
this.callbackNode = null;
41
this.callbackId = NoLanes;
42
this.callbackPriority = NoLanePriority;
43
+ this.eventTimes = createLaneMap(NoLanes);
44
this.expirationTimes = createLaneMap(NoTimestamp);
45
46
this.pendingLanes = NoLanes;
packages/react-reconciler/src/ReactFiberRoot.old.js
+1
@@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) {
40
this.callbackNode = null;
41
this.callbackId = NoLanes;
42
this.callbackPriority = NoLanePriority;
43
+ this.eventTimes = createLaneMap(NoLanes);
44
this.expirationTimes = createLaneMap(NoTimestamp);
45
46
this.pendingLanes = NoLanes;
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+61
-69
@@ -171,6 +171,7 @@ import {
171
getCurrentUpdateLanePriority,
172
markStarvedLanesAsExpired,
173
getLanesToRetrySynchronouslyOnError,
174
+ getMostRecentEventTime,
175
markRootUpdated,
176
markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
177
markRootPinged,
@@ -294,8 +295,6 @@ const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
295
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
296
// A fatal error, if one is thrown
297
let workInProgressRootFatalError: mixed = null;
297
-// Most recent event time among processed updates during this render.
298
-let workInProgressRootLatestProcessedEventTime: number = NoTimestamp;
298
let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp;
299
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
300
// "Included" lanes refer to lanes that were worked on during this render. It's
@@ -540,6 +539,35 @@ export function scheduleUpdateOnFiber(
539
return null;
540
}
541
542
+ // Mark that the root has a pending update.
543
+ markRootUpdated(root, lane, eventTime);
544
+
545
+ if (root === workInProgressRoot) {
546
+ // Received an update to a tree that's in the middle of rendering. Mark
547
+ // that there was an interleaved update work on this root. Unless the
548
+ // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
549
+ // phase update. In that case, we don't treat render phase updates as if
550
+ // they were interleaved, for backwards compat reasons.
551
+ if (
552
+ deferRenderPhaseUpdateToNextBatch ||
553
+ (executionContext & RenderContext) === NoContext
554
+ ) {
555
+ workInProgressRootUpdatedLanes = mergeLanes(
556
+ workInProgressRootUpdatedLanes,
557
+ lane,
558
+ );
559
+ }
560
+ if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
561
+ // The root already suspended with a delay, which means this render
562
+ // definitely won't finish. Since we have a new update, let's mark it as
563
+ // suspended now, right before marking the incoming update. This has the
564
+ // effect of interrupting the current render and switching to the update.
565
+ // TODO: Make sure this doesn't override pings that happen while we've
566
+ // already started rendering.
567
+ markRootSuspended(root, workInProgressRootRenderLanes);
568
+ }
569
+ }
570
+
571
// TODO: requestUpdateLanePriority also reads the priority. Pass the
572
// priority as an argument to that function and this one.
573
const priorityLevel = getCurrentPriorityLevel();
@@ -605,82 +633,47 @@ export function scheduleUpdateOnFiber(
633
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
634
// on a fiber.
635
function markUpdateLaneFromFiberToRoot(
608
- fiber: Fiber,
636
+ sourceFiber: Fiber,
637
lane: Lane,
638
): FiberRoot | null {
639
// Update the source fiber's lanes
612
- fiber.lanes = mergeLanes(fiber.lanes, lane);
613
- let alternate = fiber.alternate;
640
+ sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
641
+ let alternate = sourceFiber.alternate;
642
if (alternate !== null) {
643
alternate.lanes = mergeLanes(alternate.lanes, lane);
644
}
645
if (__DEV__) {
646
if (
647
alternate === null &&
620
- (fiber.effectTag & (Placement | Hydrating)) !== NoEffect
648
+ (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect
649
) {
622
- warnAboutUpdateOnNotYetMountedFiberInDEV(fiber);
650
+ warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
651
}
652
}
653
// Walk the parent path to the root and update the child expiration time.
626
- let node = fiber.return;
627
- let root = null;
628
- if (node === null && fiber.tag === HostRoot) {
629
- root = fiber.stateNode;
630
- } else {
631
- while (node !== null) {
632
- alternate = node.alternate;
654
+ let node = sourceFiber;
655
+ let parent = sourceFiber.return;
656
+ while (parent !== null) {
657
+ parent.childLanes = mergeLanes(parent.childLanes, lane);
658
+ alternate = parent.alternate;
659
+ if (alternate !== null) {
660
+ alternate.childLanes = mergeLanes(alternate.childLanes, lane);
661
+ } else {
662
if (__DEV__) {
634
- if (
635
- alternate === null &&
636
- (node.effectTag & (Placement | Hydrating)) !== NoEffect
637
- ) {
638
- warnAboutUpdateOnNotYetMountedFiberInDEV(fiber);
663
+ if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) {
664
+ warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
665
}
666
}
641
- node.childLanes = mergeLanes(node.childLanes, lane);
642
- if (alternate !== null) {
643
- alternate.childLanes = mergeLanes(alternate.childLanes, lane);
644
- }
645
- if (node.return === null && node.tag === HostRoot) {
646
- root = node.stateNode;
647
- break;
648
- }
649
- node = node.return;
667
}
668
+ node = parent;
669
+ parent = parent.return;
670
}
652
-
653
- if (root !== null) {
654
- // Mark that the root has a pending update.
655
- markRootUpdated(root, lane);
656
- if (workInProgressRoot === root) {
657
- // Received an update to a tree that's in the middle of rendering. Mark
658
- // that there was an interleaved update work on this root. Unless the
659
- // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
660
- // phase update. In that case, we don't treat render phase updates as if
661
- // they were interleaved, for backwards compat reasons.
662
- if (
663
- deferRenderPhaseUpdateToNextBatch ||
664
- (executionContext & RenderContext) === NoContext
665
- ) {
666
- workInProgressRootUpdatedLanes = mergeLanes(
667
- workInProgressRootUpdatedLanes,
668
- lane,
669
- );
670
- }
671
- if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
672
- // The root already suspended with a delay, which means this render
673
- // definitely won't finish. Since we have a new update, let's mark it as
674
- // suspended now, right before marking the incoming update. This has the
675
- // effect of interrupting the current render and switching to the update.
676
- // TODO: Make sure this doesn't override pings that happen while we've
677
- // already started rendering.
678
- markRootSuspended(root, workInProgressRootRenderLanes);
679
- }
680
- }
671
+ if (node.tag === HostRoot) {
672
+ const root: FiberRoot = node.stateNode;
673
+ return root;
674
+ } else {
675
+ return null;
676
}
682
-
683
- return root;
677
}
678
679
// Use this function to schedule a task for a root. There's only one task per
@@ -944,12 +937,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
937
break;
938
}
939
940
+ const mostRecentEventTime = getMostRecentEventTime(root, lanes);
941
let msUntilTimeout;
942
if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
943
// We have processed a suspense config whose expiration time we
944
// can use as the timeout.
945
msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now();
952
- } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) {
946
+ } else if (mostRecentEventTime === NoTimestamp) {
947
// This should never normally happen because only new updates
948
// cause delayed states, so we should have processed something.
949
// However, this could also happen in an offscreen tree.
@@ -957,7 +951,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
951
} else {
952
// If we didn't process a suspense config, compute a JND based on
953
// the amount of time elapsed since the most recent event time.
960
- const eventTimeMs = workInProgressRootLatestProcessedEventTime;
954
+ const eventTimeMs = mostRecentEventTime;
955
const timeElapsedMs = now() - eventTimeMs;
956
msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
957
}
@@ -980,10 +974,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
974
}
975
case RootCompleted: {
976
// The work completed. Ready to commit.
977
+ const mostRecentEventTime = getMostRecentEventTime(root, lanes);
978
if (
979
// do not delay if we're inside an act() scope
980
!shouldForceFlushFallbacksInDEV() &&
986
- workInProgressRootLatestProcessedEventTime !== NoTimestamp &&
981
+ mostRecentEventTime !== NoTimestamp &&
982
workInProgressRootCanSuspendUsingConfig !== null
983
) {
984
// If we have exceeded the minimum loading delay, which probably
@@ -991,7 +986,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
986
// a bit longer to ensure that the spinner is shown for
987
// enough time.
988
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
994
- workInProgressRootLatestProcessedEventTime,
989
+ mostRecentEventTime,
990
workInProgressRootCanSuspendUsingConfig,
991
);
992
if (msUntilTimeout > 10) {
@@ -1329,7 +1324,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1324
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1325
workInProgressRootExitStatus = RootIncomplete;
1326
workInProgressRootFatalError = null;
1332
- workInProgressRootLatestProcessedEventTime = NoTimestamp;
1327
workInProgressRootLatestSuspenseTimeout = NoTimestamp;
1328
workInProgressRootCanSuspendUsingConfig = null;
1329
workInProgressRootSkippedLanes = NoLanes;
@@ -1447,11 +1441,6 @@ export function markRenderEventTimeAndConfig(
1441
eventTime: number,
1442
suspenseConfig: null | SuspenseConfig,
1443
): void {
1450
- // Track the most recent event time of all updates processed in this batch.
1451
- if (workInProgressRootLatestProcessedEventTime < eventTime) {
1452
- workInProgressRootLatestProcessedEventTime = eventTime;
1453
- }
1454
-
1444
// Track the largest/latest timeout deadline in this batch.
1445
// TODO: If there are two transitions in the same batch, shouldn't we
1446
// choose the smaller one? Maybe this is because when an intermediate
@@ -2908,6 +2897,7 @@ function captureCommitPhaseErrorOnRoot(
2897
const eventTime = requestEventTime();
2898
const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2899
if (root !== null) {
2900
+ markRootUpdated(root, SyncLane, eventTime);
2901
ensureRootIsScheduled(root, eventTime);
2902
schedulePendingInteractions(root, SyncLane);
2903
}
@@ -2944,6 +2934,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2934
const eventTime = requestEventTime();
2935
const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2936
if (root !== null) {
2937
+ markRootUpdated(root, SyncLane, eventTime);
2938
ensureRootIsScheduled(root, eventTime);
2939
schedulePendingInteractions(root, SyncLane);
2940
}
@@ -3016,6 +3007,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
3007
const eventTime = requestEventTime();
3008
const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
3009
if (root !== null) {
3010
+ markRootUpdated(root, retryLane, eventTime);
3011
ensureRootIsScheduled(root, eventTime);
3012
schedulePendingInteractions(root, retryLane);
3013
}
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+61
-69
@@ -163,6 +163,7 @@ import {
163
getCurrentUpdateLanePriority,
164
markStarvedLanesAsExpired,
165
getLanesToRetrySynchronouslyOnError,
166
+ getMostRecentEventTime,
167
markRootUpdated,
168
markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
169
markRootPinged,
@@ -286,8 +287,6 @@ const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
287
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
288
// A fatal error, if one is thrown
289
let workInProgressRootFatalError: mixed = null;
289
-// Most recent event time among processed updates during this render.
290
-let workInProgressRootLatestProcessedEventTime: number = NoTimestamp;
290
let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp;
291
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
292
// "Included" lanes refer to lanes that were worked on during this render. It's
@@ -533,6 +532,35 @@ export function scheduleUpdateOnFiber(
532
return null;
533
}
534
535
+ // Mark that the root has a pending update.
536
+ markRootUpdated(root, lane, eventTime);
537
+
538
+ if (root === workInProgressRoot) {
539
+ // Received an update to a tree that's in the middle of rendering. Mark
540
+ // that there was an interleaved update work on this root. Unless the
541
+ // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
542
+ // phase update. In that case, we don't treat render phase updates as if
543
+ // they were interleaved, for backwards compat reasons.
544
+ if (
545
+ deferRenderPhaseUpdateToNextBatch ||
546
+ (executionContext & RenderContext) === NoContext
547
+ ) {
548
+ workInProgressRootUpdatedLanes = mergeLanes(
549
+ workInProgressRootUpdatedLanes,
550
+ lane,
551
+ );
552
+ }
553
+ if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
554
+ // The root already suspended with a delay, which means this render
555
+ // definitely won't finish. Since we have a new update, let's mark it as
556
+ // suspended now, right before marking the incoming update. This has the
557
+ // effect of interrupting the current render and switching to the update.
558
+ // TODO: Make sure this doesn't override pings that happen while we've
559
+ // already started rendering.
560
+ markRootSuspended(root, workInProgressRootRenderLanes);
561
+ }
562
+ }
563
+
564
// TODO: requestUpdateLanePriority also reads the priority. Pass the
565
// priority as an argument to that function and this one.
566
const priorityLevel = getCurrentPriorityLevel();
@@ -598,82 +626,47 @@ export function scheduleUpdateOnFiber(
626
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
627
// on a fiber.
628
function markUpdateLaneFromFiberToRoot(
601
- fiber: Fiber,
629
+ sourceFiber: Fiber,
630
lane: Lane,
631
): FiberRoot | null {
632
// Update the source fiber's lanes
605
- fiber.lanes = mergeLanes(fiber.lanes, lane);
606
- let alternate = fiber.alternate;
633
+ sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
634
+ let alternate = sourceFiber.alternate;
635
if (alternate !== null) {
636
alternate.lanes = mergeLanes(alternate.lanes, lane);
637
}
638
if (__DEV__) {
639
if (
640
alternate === null &&
613
- (fiber.effectTag & (Placement | Hydrating)) !== NoEffect
641
+ (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect
642
) {
615
- warnAboutUpdateOnNotYetMountedFiberInDEV(fiber);
643
+ warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
644
}
645
}
646
// Walk the parent path to the root and update the child expiration time.
619
- let node = fiber.return;
620
- let root = null;
621
- if (node === null && fiber.tag === HostRoot) {
622
- root = fiber.stateNode;
623
- } else {
624
- while (node !== null) {
625
- alternate = node.alternate;
647
+ let node = sourceFiber;
648
+ let parent = sourceFiber.return;
649
+ while (parent !== null) {
650
+ parent.childLanes = mergeLanes(parent.childLanes, lane);
651
+ alternate = parent.alternate;
652
+ if (alternate !== null) {
653
+ alternate.childLanes = mergeLanes(alternate.childLanes, lane);
654
+ } else {
655
if (__DEV__) {
627
- if (
628
- alternate === null &&
629
- (node.effectTag & (Placement | Hydrating)) !== NoEffect
630
- ) {
631
- warnAboutUpdateOnNotYetMountedFiberInDEV(fiber);
656
+ if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) {
657
+ warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
658
}
659
}
634
- node.childLanes = mergeLanes(node.childLanes, lane);
635
- if (alternate !== null) {
636
- alternate.childLanes = mergeLanes(alternate.childLanes, lane);
637
- }
638
- if (node.return === null && node.tag === HostRoot) {
639
- root = node.stateNode;
640
- break;
641
- }
642
- node = node.return;
660
}
661
+ node = parent;
662
+ parent = parent.return;
663
}
645
-
646
- if (root !== null) {
647
- // Mark that the root has a pending update.
648
- markRootUpdated(root, lane);
649
- if (workInProgressRoot === root) {
650
- // Received an update to a tree that's in the middle of rendering. Mark
651
- // that there was an interleaved update work on this root. Unless the
652
- // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
653
- // phase update. In that case, we don't treat render phase updates as if
654
- // they were interleaved, for backwards compat reasons.
655
- if (
656
- deferRenderPhaseUpdateToNextBatch ||
657
- (executionContext & RenderContext) === NoContext
658
- ) {
659
- workInProgressRootUpdatedLanes = mergeLanes(
660
- workInProgressRootUpdatedLanes,
661
- lane,
662
- );
663
- }
664
- if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
665
- // The root already suspended with a delay, which means this render
666
- // definitely won't finish. Since we have a new update, let's mark it as
667
- // suspended now, right before marking the incoming update. This has the
668
- // effect of interrupting the current render and switching to the update.
669
- // TODO: Make sure this doesn't override pings that happen while we've
670
- // already started rendering.
671
- markRootSuspended(root, workInProgressRootRenderLanes);
672
- }
673
- }
664
+ if (node.tag === HostRoot) {
665
+ const root: FiberRoot = node.stateNode;
666
+ return root;
667
+ } else {
668
+ return null;
669
}
675
-
676
- return root;
670
}
671
672
// Use this function to schedule a task for a root. There's only one task per
@@ -937,12 +930,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
930
break;
931
}
932
933
+ const mostRecentEventTime = getMostRecentEventTime(root, lanes);
934
let msUntilTimeout;
935
if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
936
// We have processed a suspense config whose expiration time we
937
// can use as the timeout.
938
msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now();
945
- } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) {
939
+ } else if (mostRecentEventTime === NoTimestamp) {
940
// This should never normally happen because only new updates
941
// cause delayed states, so we should have processed something.
942
// However, this could also happen in an offscreen tree.
@@ -950,7 +944,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
944
} else {
945
// If we didn't process a suspense config, compute a JND based on
946
// the amount of time elapsed since the most recent event time.
953
- const eventTimeMs = workInProgressRootLatestProcessedEventTime;
947
+ const eventTimeMs = mostRecentEventTime;
948
const timeElapsedMs = now() - eventTimeMs;
949
msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
950
}
@@ -973,10 +967,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
967
}
968
case RootCompleted: {
969
// The work completed. Ready to commit.
970
+ const mostRecentEventTime = getMostRecentEventTime(root, lanes);
971
if (
972
// do not delay if we're inside an act() scope
973
!shouldForceFlushFallbacksInDEV() &&
979
- workInProgressRootLatestProcessedEventTime !== NoTimestamp &&
974
+ mostRecentEventTime !== NoTimestamp &&
975
workInProgressRootCanSuspendUsingConfig !== null
976
) {
977
// If we have exceeded the minimum loading delay, which probably
@@ -984,7 +979,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
979
// a bit longer to ensure that the spinner is shown for
980
// enough time.
981
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
987
- workInProgressRootLatestProcessedEventTime,
982
+ mostRecentEventTime,
983
workInProgressRootCanSuspendUsingConfig,
984
);
985
if (msUntilTimeout > 10) {
@@ -1322,7 +1317,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1317
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1318
workInProgressRootExitStatus = RootIncomplete;
1319
workInProgressRootFatalError = null;
1325
- workInProgressRootLatestProcessedEventTime = NoTimestamp;
1320
workInProgressRootLatestSuspenseTimeout = NoTimestamp;
1321
workInProgressRootCanSuspendUsingConfig = null;
1322
workInProgressRootSkippedLanes = NoLanes;
@@ -1440,11 +1434,6 @@ export function markRenderEventTimeAndConfig(
1434
eventTime: number,
1435
suspenseConfig: null | SuspenseConfig,
1436
): void {
1443
- // Track the most recent event time of all updates processed in this batch.
1444
- if (workInProgressRootLatestProcessedEventTime < eventTime) {
1445
- workInProgressRootLatestProcessedEventTime = eventTime;
1446
- }
1447
-
1437
// Track the largest/latest timeout deadline in this batch.
1438
// TODO: If there are two transitions in the same batch, shouldn't we
1439
// choose the smaller one? Maybe this is because when an intermediate
@@ -2764,6 +2753,7 @@ function captureCommitPhaseErrorOnRoot(
2753
const eventTime = requestEventTime();
2754
const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2755
if (root !== null) {
2756
+ markRootUpdated(root, SyncLane, eventTime);
2757
ensureRootIsScheduled(root, eventTime);
2758
schedulePendingInteractions(root, SyncLane);
2759
}
@@ -2800,6 +2790,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2790
const eventTime = requestEventTime();
2791
const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2792
if (root !== null) {
2793
+ markRootUpdated(root, SyncLane, eventTime);
2794
ensureRootIsScheduled(root, eventTime);
2795
schedulePendingInteractions(root, SyncLane);
2796
}
@@ -2872,6 +2863,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
2863
const eventTime = requestEventTime();
2864
const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
2865
if (root !== null) {
2866
+ markRootUpdated(root, retryLane, eventTime);
2867
ensureRootIsScheduled(root, eventTime);
2868
schedulePendingInteractions(root, retryLane);
2869
}
packages/react-reconciler/src/ReactInternalTypes.js
+1
@@ -217,6 +217,7 @@ type BaseFiberRootProperties = {|
217
// if it's already working.
218
callbackId: Lanes,
219
callbackPriority: LanePriority,
220
+ eventTimes: LaneMap<number>,
221
expirationTimes: LaneMap<number>,
222
223
pendingLanes: Lanes,