@@ -226,6 +226,10 @@ let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
226226let workInProgressRootLatestProcessedExpirationTime : ExpirationTime = Sync ;
227227let workInProgressRootLatestSuspenseTimeout : ExpirationTime = Sync ;
228228let workInProgressRootCanSuspendUsingConfig : null | SuspenseConfig = null ;
229+ // The work left over by components that were visited during this render. Only
230+ // includes unprocessed updates, not work in bailed out children.
231+ let workInProgressRootNextUnprocessedUpdateTime : ExpirationTime = NoWork ;
232+
229233// If we're pinged while rendering we don't always restart immediately.
230234// This flag determines if it might be worthwhile to restart if an opportunity
231235// happens latere.
@@ -484,21 +488,27 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
484488 }
485489
486490 if ( root !== null ) {
487- if ( workInProgressRootExitStatus === RootSuspendedWithDelay ) {
488- // The root already suspended with a delay, which means this render
489- // definitely won't finish. Since we have a new update, let's mark it as
490- // suspended now, right before marking the incoming update. This has the
491- // effect of interrupting the current render and switching to the update.
492- // TODO: This happens to work when receiving an update during the render
493- // phase, because of the trick inside computeExpirationForFiber to
494- // subtract 1 from `renderExpirationTime` to move it into a
495- // separate bucket. But we should probably model it with an exception,
496- // using the same mechanism we use to force hydration of a subtree.
497- // TODO: This does not account for low pri updates that were already
498- // scheduled before the root started rendering. Need to track the next
499- // pending expiration time (perhaps by backtracking the return path) and
500- // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
501- markRootSuspendedAtTime ( root , renderExpirationTime ) ;
491+ if ( workInProgressRoot === root ) {
492+ // Received an update to a tree that's in the middle of rendering. Mark
493+ // that's unprocessed work on this root.
494+ markUnprocessedUpdateTime ( expirationTime ) ;
495+
496+ if ( workInProgressRootExitStatus === RootSuspendedWithDelay ) {
497+ // The root already suspended with a delay, which means this render
498+ // definitely won't finish. Since we have a new update, let's mark it as
499+ // suspended now, right before marking the incoming update. This has the
500+ // effect of interrupting the current render and switching to the update.
501+ // TODO: This happens to work when receiving an update during the render
502+ // phase, because of the trick inside computeExpirationForFiber to
503+ // subtract 1 from `renderExpirationTime` to move it into a
504+ // separate bucket. But we should probably model it with an exception,
505+ // using the same mechanism we use to force hydration of a subtree.
506+ // TODO: This does not account for low pri updates that were already
507+ // scheduled before the root started rendering. Need to track the next
508+ // pending expiration time (perhaps by backtracking the return path) and
509+ // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
510+ markRootSuspendedAtTime ( root , renderExpirationTime ) ;
511+ }
502512 }
503513 // Mark that the root has a pending update.
504514 markRootUpdatedAtTime ( root , expirationTime ) ;
@@ -856,6 +866,7 @@ function prepareFreshStack(root, expirationTime) {
856866 workInProgressRootLatestProcessedExpirationTime = Sync ;
857867 workInProgressRootLatestSuspenseTimeout = Sync ;
858868 workInProgressRootCanSuspendUsingConfig = null ;
869+ workInProgressRootNextUnprocessedUpdateTime = NoWork ;
859870 workInProgressRootHasPendingPing = false ;
860871
861872 if ( enableSchedulerTracing ) {
@@ -1255,42 +1266,42 @@ export function markRenderEventTimeAndConfig(
12551266 }
12561267}
12571268
1269+ export function markUnprocessedUpdateTime (
1270+ expirationTime : ExpirationTime ,
1271+ ) : void {
1272+ if ( expirationTime > workInProgressRootNextUnprocessedUpdateTime ) {
1273+ workInProgressRootNextUnprocessedUpdateTime = expirationTime ;
1274+ }
1275+ }
1276+
12581277export function renderDidSuspend ( ) : void {
12591278 if ( workInProgressRootExitStatus === RootIncomplete ) {
12601279 workInProgressRootExitStatus = RootSuspended ;
12611280 }
12621281}
12631282
1264- export function renderDidSuspendDelayIfPossible ( suspendedWork : Fiber ) : void {
1283+ export function renderDidSuspendDelayIfPossible ( ) : void {
12651284 if (
12661285 workInProgressRootExitStatus === RootIncomplete ||
12671286 workInProgressRootExitStatus === RootSuspended
12681287 ) {
12691288 workInProgressRootExitStatus = RootSuspendedWithDelay ;
12701289 }
12711290
1272- if ( workInProgressRoot !== null ) {
1273- // Check if the component that suspsended, or any components in the return
1274- // path, have a pending update. If so, those updates might unsuspend us, so
1275- // interrupt the current render and restart.
1276- let nextAfterSuspendedTime = NoWork ;
1277- let fiber = suspendedWork ;
1278- while ( fiber !== null ) {
1279- const updateExpirationTime = fiber . expirationTime ;
1280- if ( updateExpirationTime > nextAfterSuspendedTime ) {
1281- nextAfterSuspendedTime = updateExpirationTime ;
1282- }
1283- fiber = fiber . return ;
1284- }
1285-
1286- if ( nextAfterSuspendedTime !== NoWork ) {
1287- // Mark the current render as suspended, and then mark that there's a
1288- // pending update.
1289- // TODO: This should immediately interrupt the current render, instead
1290- // of waiting until the next time we yield.
1291- markRootSuspendedAtTime ( workInProgressRoot , renderExpirationTime ) ;
1292- markRootUpdatedAtTime ( workInProgressRoot , nextAfterSuspendedTime ) ;
1293- }
1291+ // Check if there's a lower priority update somewhere else in the tree.
1292+ if (
1293+ workInProgressRootNextUnprocessedUpdateTime !== NoWork &&
1294+ workInProgressRoot !== null
1295+ ) {
1296+ // Mark the current render as suspended, and then mark that there's a
1297+ // pending update.
1298+ // TODO: This should immediately interrupt the current render, instead
1299+ // of waiting until the next time we yield.
1300+ markRootSuspendedAtTime ( workInProgressRoot , renderExpirationTime ) ;
1301+ markRootUpdatedAtTime (
1302+ workInProgressRoot ,
1303+ workInProgressRootNextUnprocessedUpdateTime ,
1304+ ) ;
12941305 }
12951306}
12961307
0 commit comments