From 99f772800fd5f6772c8771b36770f40100d93bef Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 16 Jul 2020 17:28:22 -0500 Subject: [PATCH 1/2] Pass event time to markRootUpdated Some minor rearranging so that eventTime gets threaded through. No change in behavior. --- .../react-reconciler/src/ReactFiberLane.js | 6 +- .../src/ReactFiberWorkLoop.new.js | 111 +++++++++--------- .../src/ReactFiberWorkLoop.old.js | 111 +++++++++--------- 3 files changed, 113 insertions(+), 115 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index bc30a468f7d99..0520b3daca7b6 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -648,7 +648,11 @@ export function createLaneMap(initial: T): LaneMap { return new Array(TotalLanes).fill(initial); } -export function markRootUpdated(root: FiberRoot, updateLane: Lane) { +export function markRootUpdated( + root: FiberRoot, + updateLane: Lane, + eventTime: number, +) { root.pendingLanes |= updateLane; // TODO: Theoretically, any update to any lane can unblock any other lane. But diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 535100dff2d7f..873553a206719 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -540,6 +540,35 @@ export function scheduleUpdateOnFiber( return null; } + // Mark that the root has a pending update. + markRootUpdated(root, lane, eventTime); + + if (root === workInProgressRoot) { + // Received an update to a tree that's in the middle of rendering. Mark + // that there was an interleaved update work on this root. Unless the + // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render + // phase update. In that case, we don't treat render phase updates as if + // they were interleaved, for backwards compat reasons. + if ( + deferRenderPhaseUpdateToNextBatch || + (executionContext & RenderContext) === NoContext + ) { + workInProgressRootUpdatedLanes = mergeLanes( + workInProgressRootUpdatedLanes, + lane, + ); + } + if (workInProgressRootExitStatus === RootSuspendedWithDelay) { + // The root already suspended with a delay, which means this render + // definitely won't finish. Since we have a new update, let's mark it as + // suspended now, right before marking the incoming update. This has the + // effect of interrupting the current render and switching to the update. + // TODO: Make sure this doesn't override pings that happen while we've + // already started rendering. + markRootSuspended(root, workInProgressRootRenderLanes); + } + } + // TODO: requestUpdateLanePriority also reads the priority. Pass the // priority as an argument to that function and this one. const priorityLevel = getCurrentPriorityLevel(); @@ -605,82 +634,47 @@ export function scheduleUpdateOnFiber( // e.g. retrying a Suspense boundary isn't an update, but it does schedule work // on a fiber. function markUpdateLaneFromFiberToRoot( - fiber: Fiber, + sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { // Update the source fiber's lanes - fiber.lanes = mergeLanes(fiber.lanes, lane); - let alternate = fiber.alternate; + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } if (__DEV__) { if ( alternate === null && - (fiber.effectTag & (Placement | Hydrating)) !== NoEffect + (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } // Walk the parent path to the root and update the child expiration time. - let node = fiber.return; - let root = null; - if (node === null && fiber.tag === HostRoot) { - root = fiber.stateNode; - } else { - while (node !== null) { - alternate = node.alternate; + let node = sourceFiber; + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } else { if (__DEV__) { - if ( - alternate === null && - (node.effectTag & (Placement | Hydrating)) !== NoEffect - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } - node.childLanes = mergeLanes(node.childLanes, lane); - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } - if (node.return === null && node.tag === HostRoot) { - root = node.stateNode; - break; - } - node = node.return; } + node = parent; + parent = parent.return; } - - if (root !== null) { - // Mark that the root has a pending update. - markRootUpdated(root, lane); - if (workInProgressRoot === root) { - // Received an update to a tree that's in the middle of rendering. Mark - // that there was an interleaved update work on this root. Unless the - // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render - // phase update. In that case, we don't treat render phase updates as if - // they were interleaved, for backwards compat reasons. - if ( - deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext - ) { - workInProgressRootUpdatedLanes = mergeLanes( - workInProgressRootUpdatedLanes, - lane, - ); - } - if (workInProgressRootExitStatus === RootSuspendedWithDelay) { - // The root already suspended with a delay, which means this render - // definitely won't finish. Since we have a new update, let's mark it as - // suspended now, right before marking the incoming update. This has the - // effect of interrupting the current render and switching to the update. - // TODO: Make sure this doesn't override pings that happen while we've - // already started rendering. - markRootSuspended(root, workInProgressRootRenderLanes); - } - } + if (node.tag === HostRoot) { + const root: FiberRoot = node.stateNode; + return root; + } else { + return null; } - - return root; } // Use this function to schedule a task for a root. There's only one task per @@ -2908,6 +2902,7 @@ function captureCommitPhaseErrorOnRoot( const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2944,6 +2939,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -3016,6 +3012,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); if (root !== null) { + markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, retryLane); } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 42811a73b1f56..067d1d31230ff 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -533,6 +533,35 @@ export function scheduleUpdateOnFiber( return null; } + // Mark that the root has a pending update. + markRootUpdated(root, lane, eventTime); + + if (root === workInProgressRoot) { + // Received an update to a tree that's in the middle of rendering. Mark + // that there was an interleaved update work on this root. Unless the + // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render + // phase update. In that case, we don't treat render phase updates as if + // they were interleaved, for backwards compat reasons. + if ( + deferRenderPhaseUpdateToNextBatch || + (executionContext & RenderContext) === NoContext + ) { + workInProgressRootUpdatedLanes = mergeLanes( + workInProgressRootUpdatedLanes, + lane, + ); + } + if (workInProgressRootExitStatus === RootSuspendedWithDelay) { + // The root already suspended with a delay, which means this render + // definitely won't finish. Since we have a new update, let's mark it as + // suspended now, right before marking the incoming update. This has the + // effect of interrupting the current render and switching to the update. + // TODO: Make sure this doesn't override pings that happen while we've + // already started rendering. + markRootSuspended(root, workInProgressRootRenderLanes); + } + } + // TODO: requestUpdateLanePriority also reads the priority. Pass the // priority as an argument to that function and this one. const priorityLevel = getCurrentPriorityLevel(); @@ -598,82 +627,47 @@ export function scheduleUpdateOnFiber( // e.g. retrying a Suspense boundary isn't an update, but it does schedule work // on a fiber. function markUpdateLaneFromFiberToRoot( - fiber: Fiber, + sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { // Update the source fiber's lanes - fiber.lanes = mergeLanes(fiber.lanes, lane); - let alternate = fiber.alternate; + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } if (__DEV__) { if ( alternate === null && - (fiber.effectTag & (Placement | Hydrating)) !== NoEffect + (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } // Walk the parent path to the root and update the child expiration time. - let node = fiber.return; - let root = null; - if (node === null && fiber.tag === HostRoot) { - root = fiber.stateNode; - } else { - while (node !== null) { - alternate = node.alternate; + let node = sourceFiber; + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } else { if (__DEV__) { - if ( - alternate === null && - (node.effectTag & (Placement | Hydrating)) !== NoEffect - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } - node.childLanes = mergeLanes(node.childLanes, lane); - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } - if (node.return === null && node.tag === HostRoot) { - root = node.stateNode; - break; - } - node = node.return; } + node = parent; + parent = parent.return; } - - if (root !== null) { - // Mark that the root has a pending update. - markRootUpdated(root, lane); - if (workInProgressRoot === root) { - // Received an update to a tree that's in the middle of rendering. Mark - // that there was an interleaved update work on this root. Unless the - // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render - // phase update. In that case, we don't treat render phase updates as if - // they were interleaved, for backwards compat reasons. - if ( - deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext - ) { - workInProgressRootUpdatedLanes = mergeLanes( - workInProgressRootUpdatedLanes, - lane, - ); - } - if (workInProgressRootExitStatus === RootSuspendedWithDelay) { - // The root already suspended with a delay, which means this render - // definitely won't finish. Since we have a new update, let's mark it as - // suspended now, right before marking the incoming update. This has the - // effect of interrupting the current render and switching to the update. - // TODO: Make sure this doesn't override pings that happen while we've - // already started rendering. - markRootSuspended(root, workInProgressRootRenderLanes); - } - } + if (node.tag === HostRoot) { + const root: FiberRoot = node.stateNode; + return root; + } else { + return null; } - - return root; } // Use this function to schedule a task for a root. There's only one task per @@ -2764,6 +2758,7 @@ function captureCommitPhaseErrorOnRoot( const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2800,6 +2795,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2872,6 +2868,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); if (root !== null) { + markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, retryLane); } From 6a2703c593171e9e2583638717f63584eb840f01 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 16 Jul 2020 18:16:37 -0500 Subject: [PATCH 2/2] 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. --- .../react-reconciler/src/ReactFiberLane.js | 38 ++++++++++++++++++- .../src/ReactFiberRoot.new.js | 1 + .../src/ReactFiberRoot.old.js | 1 + .../src/ReactFiberWorkLoop.new.js | 19 ++++------ .../src/ReactFiberWorkLoop.old.js | 19 ++++------ .../src/ReactInternalTypes.js | 1 + 6 files changed, 53 insertions(+), 26 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 0520b3daca7b6..fc74f7782a4e2 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -374,6 +374,25 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { return nextLanes; } +export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number { + const eventTimes = root.eventTimes; + + let mostRecentEventTime = NoTimestamp; + while (lanes > 0) { + const index = pickArbitraryLaneIndex(lanes); + const lane = 1 << index; + + const eventTime = eventTimes[index]; + if (eventTime > mostRecentEventTime) { + mostRecentEventTime = eventTime; + } + + lanes &= ~lane; + } + + return mostRecentEventTime; +} + function computeExpirationTime(lane: Lane, currentTime: number) { // TODO: Expiration heuristic is constant per lane, so could use a map. getHighestPriorityLanes(lane); @@ -606,10 +625,14 @@ export function pickArbitraryLane(lanes: Lanes): Lane { return getHighestPriorityLane(lanes); } -function pickArbitraryLaneIndex(lanes: Lane | Lanes) { +function pickArbitraryLaneIndex(lanes: Lanes) { return 31 - clz32(lanes); } +function laneToIndex(lane: Lane) { + return pickArbitraryLaneIndex(lane); +} + export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) { return (a & b) !== NoLanes; } @@ -670,6 +693,12 @@ export function markRootUpdated( root.suspendedLanes &= higherPriorityLanes; root.pingedLanes &= higherPriorityLanes; + + const eventTimes = root.eventTimes; + const index = laneToIndex(updateLane); + // We can always overwrite an existing timestamp because we prefer the most + // recent event, and we assume time is monotonically increasing. + eventTimes[index] = eventTime; } export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { @@ -727,13 +756,18 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { root.entangledLanes &= remainingLanes; + const entanglements = root.entanglements; + const eventTimes = root.eventTimes; const expirationTimes = root.expirationTimes; + + // Clear the lanes that no longer have pending work let lanes = noLongerPendingLanes; while (lanes > 0) { const index = pickArbitraryLaneIndex(lanes); const lane = 1 << index; - // Clear the expiration time + entanglements[index] = NoLanes; + eventTimes[index] = NoTimestamp; expirationTimes[index] = NoTimestamp; lanes &= ~lane; diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 31603c68d41e4..eaa4aba574d81 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.callbackNode = null; this.callbackId = NoLanes; this.callbackPriority = NoLanePriority; + this.eventTimes = createLaneMap(NoLanes); this.expirationTimes = createLaneMap(NoTimestamp); this.pendingLanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 02506c7d41096..276208db25834 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.callbackNode = null; this.callbackId = NoLanes; this.callbackPriority = NoLanePriority; + this.eventTimes = createLaneMap(NoLanes); this.expirationTimes = createLaneMap(NoTimestamp); this.pendingLanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 873553a206719..bf6e813b2d2fe 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -171,6 +171,7 @@ import { getCurrentUpdateLanePriority, markStarvedLanesAsExpired, getLanesToRetrySynchronouslyOnError, + getMostRecentEventTime, markRootUpdated, markRootSuspended as markRootSuspended_dontCallThisOneDirectly, markRootPinged, @@ -294,8 +295,6 @@ const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes); let workInProgressRootExitStatus: RootExitStatus = RootIncomplete; // A fatal error, if one is thrown let workInProgressRootFatalError: mixed = null; -// Most recent event time among processed updates during this render. -let workInProgressRootLatestProcessedEventTime: number = NoTimestamp; let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp; let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null; // "Included" lanes refer to lanes that were worked on during this render. It's @@ -938,12 +937,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { break; } + const mostRecentEventTime = getMostRecentEventTime(root, lanes); let msUntilTimeout; if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) { // We have processed a suspense config whose expiration time we // can use as the timeout. msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now(); - } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) { + } else if (mostRecentEventTime === NoTimestamp) { // This should never normally happen because only new updates // cause delayed states, so we should have processed something. // However, this could also happen in an offscreen tree. @@ -951,7 +951,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } else { // If we didn't process a suspense config, compute a JND based on // the amount of time elapsed since the most recent event time. - const eventTimeMs = workInProgressRootLatestProcessedEventTime; + const eventTimeMs = mostRecentEventTime; const timeElapsedMs = now() - eventTimeMs; msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; } @@ -974,10 +974,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } case RootCompleted: { // The work completed. Ready to commit. + const mostRecentEventTime = getMostRecentEventTime(root, lanes); if ( // do not delay if we're inside an act() scope !shouldForceFlushFallbacksInDEV() && - workInProgressRootLatestProcessedEventTime !== NoTimestamp && + mostRecentEventTime !== NoTimestamp && workInProgressRootCanSuspendUsingConfig !== null ) { // If we have exceeded the minimum loading delay, which probably @@ -985,7 +986,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { // a bit longer to ensure that the spinner is shown for // enough time. const msUntilTimeout = computeMsUntilSuspenseLoadingDelay( - workInProgressRootLatestProcessedEventTime, + mostRecentEventTime, workInProgressRootCanSuspendUsingConfig, ); if (msUntilTimeout > 10) { @@ -1323,7 +1324,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; workInProgressRootExitStatus = RootIncomplete; workInProgressRootFatalError = null; - workInProgressRootLatestProcessedEventTime = NoTimestamp; workInProgressRootLatestSuspenseTimeout = NoTimestamp; workInProgressRootCanSuspendUsingConfig = null; workInProgressRootSkippedLanes = NoLanes; @@ -1441,11 +1441,6 @@ export function markRenderEventTimeAndConfig( eventTime: number, suspenseConfig: null | SuspenseConfig, ): void { - // Track the most recent event time of all updates processed in this batch. - if (workInProgressRootLatestProcessedEventTime < eventTime) { - workInProgressRootLatestProcessedEventTime = eventTime; - } - // Track the largest/latest timeout deadline in this batch. // TODO: If there are two transitions in the same batch, shouldn't we // choose the smaller one? Maybe this is because when an intermediate diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 067d1d31230ff..ea8c7b1a37bda 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -163,6 +163,7 @@ import { getCurrentUpdateLanePriority, markStarvedLanesAsExpired, getLanesToRetrySynchronouslyOnError, + getMostRecentEventTime, markRootUpdated, markRootSuspended as markRootSuspended_dontCallThisOneDirectly, markRootPinged, @@ -286,8 +287,6 @@ const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes); let workInProgressRootExitStatus: RootExitStatus = RootIncomplete; // A fatal error, if one is thrown let workInProgressRootFatalError: mixed = null; -// Most recent event time among processed updates during this render. -let workInProgressRootLatestProcessedEventTime: number = NoTimestamp; let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp; let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null; // "Included" lanes refer to lanes that were worked on during this render. It's @@ -931,12 +930,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { break; } + const mostRecentEventTime = getMostRecentEventTime(root, lanes); let msUntilTimeout; if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) { // We have processed a suspense config whose expiration time we // can use as the timeout. msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now(); - } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) { + } else if (mostRecentEventTime === NoTimestamp) { // This should never normally happen because only new updates // cause delayed states, so we should have processed something. // However, this could also happen in an offscreen tree. @@ -944,7 +944,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } else { // If we didn't process a suspense config, compute a JND based on // the amount of time elapsed since the most recent event time. - const eventTimeMs = workInProgressRootLatestProcessedEventTime; + const eventTimeMs = mostRecentEventTime; const timeElapsedMs = now() - eventTimeMs; msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; } @@ -967,10 +967,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } case RootCompleted: { // The work completed. Ready to commit. + const mostRecentEventTime = getMostRecentEventTime(root, lanes); if ( // do not delay if we're inside an act() scope !shouldForceFlushFallbacksInDEV() && - workInProgressRootLatestProcessedEventTime !== NoTimestamp && + mostRecentEventTime !== NoTimestamp && workInProgressRootCanSuspendUsingConfig !== null ) { // If we have exceeded the minimum loading delay, which probably @@ -978,7 +979,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { // a bit longer to ensure that the spinner is shown for // enough time. const msUntilTimeout = computeMsUntilSuspenseLoadingDelay( - workInProgressRootLatestProcessedEventTime, + mostRecentEventTime, workInProgressRootCanSuspendUsingConfig, ); if (msUntilTimeout > 10) { @@ -1316,7 +1317,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; workInProgressRootExitStatus = RootIncomplete; workInProgressRootFatalError = null; - workInProgressRootLatestProcessedEventTime = NoTimestamp; workInProgressRootLatestSuspenseTimeout = NoTimestamp; workInProgressRootCanSuspendUsingConfig = null; workInProgressRootSkippedLanes = NoLanes; @@ -1434,11 +1434,6 @@ export function markRenderEventTimeAndConfig( eventTime: number, suspenseConfig: null | SuspenseConfig, ): void { - // Track the most recent event time of all updates processed in this batch. - if (workInProgressRootLatestProcessedEventTime < eventTime) { - workInProgressRootLatestProcessedEventTime = eventTime; - } - // Track the largest/latest timeout deadline in this batch. // TODO: If there are two transitions in the same batch, shouldn't we // choose the smaller one? Maybe this is because when an intermediate diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 1a638d42d5a0f..51b8f9ec188bb 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -217,6 +217,7 @@ type BaseFiberRootProperties = {| // if it's already working. callbackId: Lanes, callbackPriority: LanePriority, + eventTimes: LaneMap, expirationTimes: LaneMap, pendingLanes: Lanes,