Skip to content

Commit

Permalink
Remove JND delay for non-transition updates (#26597)
Browse files Browse the repository at this point in the history
Updates that are marked as part of a transition are allowed to block a
render from committing. Generally, other updates cannot — however,
there's one exception that's leftover from a previous iteration of our
Suspense architecture. If an update is not the result of a known urgent
event type — known as "Default" updates — then we allow it to suspend
briefly, as long as the delay is short enough that the user won't
notice. We refer to this delay as a "Just Noticable Difference" (JND)
delay. To illustrate, if the user has already waited 400ms for an update
to be reflected on the screen, the theory is that they won't notice if
you wait an additional 100ms. So React can suspend for a bit longer in
case more data comes in. The longer the user has already waited, the
longer the JND.

While we still believe this theory is sound from a UX perspective, we no
longer think the implementation complexity is worth it. The main thing
that's changed is how we handle Default updates. We used to render
Default updates concurrently (i.e. they were time sliced, and were
scheduled with postTask), but now they are blocking. Soon, they will
also be scheduled with rAF, too, which means by the end of the next rAF,
they will have either finished rendering or the main thread will be
blocked until they do. There are various motivations for this but part
of the rationale is that anything that can be made non-blocking should
be marked as a Transition, anyway, so it's not worth adding
implementation complexity to Default.

This commit removes the JND delay for Default updates. They will now
commit immediately once the render phase is complete, even if a
component suspends.

DiffTrain build for commit 0b931f9.
  • Loading branch information
acdlite committed Apr 11, 2023
1 parent 4b054d3 commit c67e7ff
Show file tree
Hide file tree
Showing 13 changed files with 938 additions and 1,403 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1342,24 +1342,6 @@ function getNextLanes(root, wipLanes) {

return nextLanes;
}
function getMostRecentEventTime(root, lanes) {
var eventTimes = root.eventTimes;
var mostRecentEventTime = NoTimestamp;

while (lanes > 0) {
var index = pickArbitraryLaneIndex(lanes);
var lane = 1 << index;
var eventTime = eventTimes[index];

if (eventTime > mostRecentEventTime) {
mostRecentEventTime = eventTime;
}

lanes &= ~lane;
}

return mostRecentEventTime;
}

function computeExpirationTime(lane, currentTime) {
switch (lane) {
Expand Down Expand Up @@ -19771,7 +19753,6 @@ function scheduleImmediateTask(cb) {
}
}

var ceil = Math.ceil;
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher,
ReactCurrentCache = ReactSharedInternals.ReactCurrentCache,
Expand Down Expand Up @@ -20395,37 +20376,6 @@ function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
break;
}

if (!shouldForceFlushFallbacksInDEV()) {
// This is not a transition, but we did trigger an avoided state.
// Schedule a placeholder to display after a short delay, using the Just
// Noticeable Difference.
// TODO: Is the JND optimization worth the added complexity? If this is
// the only reason we track the event time, then probably not.
// Consider removing.
var mostRecentEventTime = getMostRecentEventTime(root, lanes);
var eventTimeMs = mostRecentEventTime;
var timeElapsedMs = now$1() - eventTimeMs;

var _msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; // Don't bother with a very short suspense time.

if (_msUntilTimeout > 10) {
// Instead of committing the fallback immediately, wait for more data
// to arrive.
root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
),
_msUntilTimeout
);
break;
}
} // Commit the placeholder.

commitRootWhenReady(
Expand Down Expand Up @@ -22365,32 +22315,7 @@ function resolveRetryWakeable(boundaryFiber, wakeable) {
}

retryTimedOutBoundary(boundaryFiber, retryLane);
} // Computes the next Just Noticeable Difference (JND) boundary.
// The theory is that a person can't tell the difference between small differences in time.
// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
// difference in the experience. However, waiting for longer might mean that we can avoid
// showing an intermediate loading state. The longer we have already waited, the harder it
// is to tell small differences in time. Therefore, the longer we've already waited,
// the longer we can wait additionally. At some point we have to give up though.
// We pick a train model where the next boundary commits at a consistent schedule.
// These particular numbers are vague estimates. We expect to adjust them based on research.

function jnd(timeElapsed) {
return timeElapsed < 120
? 120
: timeElapsed < 480
? 480
: timeElapsed < 1080
? 1080
: timeElapsed < 1920
? 1920
: timeElapsed < 3000
? 3000
: timeElapsed < 4320
? 4320
: ceil(timeElapsed / 1960) * 1960;
}

function throwIfInfiniteUpdateLoopDetected() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
Expand Down Expand Up @@ -23872,7 +23797,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-next-ac43bf687-20230410";
var ReactVersion = "18.3.0-next-0b931f90e-20230411";

// Might add PROFILE later.

Expand Down
Loading

0 comments on commit c67e7ff

Please sign in to comment.