9
10
import type {Thenable, Wakeable} from 'shared/ReactTypes';
11
import type {Fiber, FiberRoot} from './ReactInternalTypes';
12
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
12
+import type {Lanes, Lane} from './ReactFiberLane';
13
import type {ReactPriorityLevel} from './ReactInternalTypes';
14
import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
43
ImmediatePriority as ImmediateSchedulerPriority,
44
UserBlockingPriority as UserBlockingSchedulerPriority,
45
NormalPriority as NormalSchedulerPriority,
46
- LowPriority as LowSchedulerPriority,
47
- IdlePriority as IdleSchedulerPriority,
46
flushSyncCallbackQueue,
47
scheduleSyncCallback,
48
} from './SchedulerWithReactIntegration.new';
67
createWorkInProgress,
68
assignFiberPropertiesInDEV,
69
} from './ReactFiber.new';
72
-import {
73
- isRootSuspendedAtTime,
74
- markRootSuspendedAtTime,
75
- markRootFinishedAtTime,
76
- markRootUpdatedAtTime,
77
- markRootExpiredAtTime,
78
-} from './ReactFiberRoot.new';
70
import {
71
NoMode,
72
StrictMode,
84
MemoComponent,
85
SimpleMemoComponent,
86
Block,
87
+ OffscreenComponent,
88
+ LegacyHiddenComponent,
89
} from './ReactWorkTags';
90
import {LegacyRoot} from './ReactRootTags';
91
import {
106
Hydrating,
107
HydratingAndUpdate,
108
} from './ReactSideEffectTags';
116
-import {
117
- NoWork,
118
- Sync,
119
- UserBlockingUpdateTime,
120
- DefaultUpdateTime,
121
- Never,
122
- inferPriorityFromExpirationTime,
123
- Batched,
124
- Idle,
125
- ContinuousHydration,
126
- ShortTransition,
127
- LongTransition,
128
- isSameOrHigherPriority,
129
- isSameExpirationTime,
130
- bumpPriorityLower,
131
-} from './ReactFiberExpirationTime.new';
109
import {
110
SyncLanePriority,
134
- SyncBatchedLanePriority,
111
InputDiscreteLanePriority,
136
- InputContinuousLanePriority,
137
- DefaultLanePriority,
112
TransitionShortLanePriority,
113
TransitionLongLanePriority,
140
- HydrationContinuousLanePriority,
141
- IdleLanePriority,
142
- OffscreenLanePriority,
114
+ NoLanes,
115
+ NoLane,
116
+ SyncLane,
117
+ SyncBatchedLane,
118
+ OffscreenLane,
119
+ findUpdateLane,
120
+ findTransitionLane,
121
+ includesSomeLane,
122
+ isSubsetOfLanes,
123
+ mergeLanes,
124
+ removeLanes,
125
+ hasDiscreteLanes,
126
+ hasUpdatePriority,
127
+ getNextLanes,
128
+ returnNextLanesPriority,
129
+ getLanesToRetrySynchronouslyOnError,
130
+ markRootUpdated,
131
+ markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
132
+ markRootPinged,
133
+ markRootExpired,
134
+ markDiscreteUpdatesExpired,
135
+ markRootFinished,
136
+ schedulerPriorityToLanePriority,
137
+ lanePriorityToSchedulerPriority,
138
} from './ReactFiberLane';
139
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
140
import {completeWork} from './ReactFiberCompleteWork.new';
230
let workInProgressRoot: FiberRoot | null = null;
231
// The fiber we're working on
232
let workInProgress: Fiber | null = null;
238
-// The expiration time we're rendering
239
-let renderExpirationTime: ExpirationTimeOpaque = NoWork;
240
-
241
-// Stack that allows components to channge renderExpirationTime for its subtree
242
-const renderExpirationTimeCursor: StackCursor<ExpirationTimeOpaque> = createCursor(
243
- NoWork,
244
-);
233
+// The lanes we're rendering
234
+let workInProgressRootRenderLanes: Lanes = NoLanes;
235
+
236
+// Stack that allows components to change the render lanes for its subtree
237
+// This is a superset of the lanes we started working on at the root. The only
238
+// case where it's different from `workInProgressRootRenderLanes` is when we
239
+// enter a subtree that is hidden and needs to be unhidden: Suspense and
240
+// Offscreen component.
241
+//
242
+// Most things in the work loop should deal with workInProgressRootRenderLanes.
243
+// Most things in begin/complete phases should deal with subtreeRenderLanes.
244
+let subtreeRenderLanes: Lanes = NoLanes;
245
+const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
246
247
// Whether to root completed, errored, suspended, etc.
248
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
249
// A fatal error, if one is thrown
250
let workInProgressRootFatalError: mixed = null;
251
// Most recent event time among processed updates during this render.
251
-// This is conceptually a time stamp but expressed in terms of an ExpirationTimeOpaque
252
-// because we deal mostly with expiration times in the hot path, so this avoids
253
-// the conversion happening in the hot path.
252
let workInProgressRootLatestProcessedEventTime: number = -1;
253
let workInProgressRootLatestSuspenseTimeout: number = -1;
254
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
255
+// "Included" lanes refer to lanes that were worked on during this render. It's
256
+// slightly different than `renderLanes` because `renderLanes` can change as you
257
+// enter and exit an Offscreen tree. This value is the combination of all render
258
+// lanes for the entire render phase.
259
+let workInProgressRootIncludedLanes: Lanes = NoLanes;
260
// The work left over by components that were visited during this render. Only
261
// includes unprocessed updates, not work in bailed out children.
259
-let workInProgressRootNextUnprocessedUpdateTime: ExpirationTimeOpaque = NoWork;
262
+let workInProgressRootSkippedLanes: Lanes = NoLanes;
263
+// Lanes that were updated (in an interleaved event) during this render.
264
+let workInProgressRootUpdatedLanes: Lanes = NoLanes;
265
+// Lanes that were pinged (in an interleaved event) during this render.
266
+let workInProgressRootPingedLanes: Lanes = NoLanes;
267
+
268
+let mostRecentlyUpdatedRoot: FiberRoot | null = null;
269
261
-// If we're pinged while rendering we don't always restart immediately.
262
-// This flag determines if it might be worthwhile to restart if an opportunity
263
-// happens latere.
264
-let workInProgressRootHasPendingPing: boolean = false;
270
// The most recent time we committed a fallback. This lets us ensure a train
271
// model where we don't commit new loading states in too quick succession.
272
let globalMostRecentFallbackTime: number = 0;
281
let rootDoesHavePassiveEffects: boolean = false;
282
let rootWithPendingPassiveEffects: FiberRoot | null = null;
283
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
279
-let pendingPassiveEffectsExpirationTime: ExpirationTimeOpaque = NoWork;
284
+let pendingPassiveEffectsLanes: Lanes = NoLanes;
285
let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
286
let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
287
let pendingPassiveProfilerEffects: Array<Fiber> = [];
288
284
-let rootsWithPendingDiscreteUpdates: Map<
285
- FiberRoot,
286
- ExpirationTimeOpaque,
287
-> | null = null;
289
+let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
290
291
// Use these to prevent an infinite loop of nested updates
292
const NESTED_UPDATE_LIMIT = 50;
296
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
297
let nestedPassiveUpdateCount: number = 0;
298
297
-// Marks the need to reschedule pending interactions at these expiration times
299
+// Marks the need to reschedule pending interactions at these lanes
300
// during the commit phase. This enables them to be traced across components
301
// that spawn new work during render. E.g. hidden boundaries, suspended SSR
302
// hydration or SuspenseList.
301
-let spawnedWorkDuringRender: null | Array<ExpirationTimeOpaque> = null;
303
+// TODO: Can use a bitmask instead of an array
304
+let spawnedWorkDuringRender: null | Array<Lane | Lanes> = null;
305
306
// If two updates are scheduled within the same event, we should treat their
307
// event times as simultaneous, even if the actual clock time has advanced
308
// between the first and second call.
309
let currentEventTime: number = -1;
310
+let currentEventWipLanes: Lanes = NoLanes;
311
+let currentEventPendingLanes: Lanes = NoLanes;
312
313
// Dev only flag that tracks if passive effects are currently being flushed.
314
// We warn about state updates for unmounted components differently in this case.
340
return now();
341
}
342
338
-export function requestUpdateExpirationTime(
343
+export function requestUpdateLane(
344
fiber: Fiber,
345
suspenseConfig: SuspenseConfig | null,
341
-): ExpirationTimeOpaque {
346
+): Lane {
347
// Special cases
348
const mode = fiber.mode;
349
if ((mode & BlockingMode) === NoMode) {
345
- return Sync;
350
+ return (SyncLane: Lane);
351
} else if ((mode & ConcurrentMode) === NoMode) {
352
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
348
- ? (Sync: ExpirationTimeOpaque)
349
- : Batched;
350
- } else if ((executionContext & RenderContext) !== NoContext) {
351
- // Use whatever time we're already rendering
352
- // TODO: Treat render phase updates as if they came from an
353
- // interleaved event.
354
- return renderExpirationTime;
355
- }
356
-
357
- let updateLanePriority;
353
+ ? (SyncLane: Lane)
354
+ : (SyncBatchedLane: Lane);
355
+ }
356
+
357
+ // The algorithm for assigning an update to a lane should be stable for all
358
+ // updates at the same priority within the same event. To do this, the inputs
359
+ // to the algorithm must be the same. For example, we use the `renderLanes`
360
+ // to avoid choosing a lane that is already in the middle of rendering.
361
+ //
362
+ // However, the "included" lanes could be mutated in between updates in the
363
+ // same event, like if you perform an update inside `flushSync`. Or any other
364
+ // codepath that might call `prepareFreshStack`.
365
+ //
366
+ // The trick we use is to cache the first of each of these inputs within an
367
+ // event. Then reset the cached values once we can be sure the event is over.
368
+ // Our heuristic for that is whenever we enter a concurrent work loop.
369
+ //
370
+ // We'll do the same for `currentEventPendingLanes` below.
371
+ if (currentEventWipLanes === NoLanes) {
372
+ currentEventWipLanes = workInProgressRootIncludedLanes;
373
+ }
374
+
375
+ let lane;
376
if (suspenseConfig !== null) {
359
- // If there's a SuspenseConfig, choose an expiration time that's lower
360
- // priority than a normal concurrent update (regardless of the current
361
- // Scheduler priority.) Timeouts larger than 10 seconds move one level
362
- // lower than that.
377
+ // Use the size of the timeout as a heuristic to prioritize shorter
378
+ // transitions over longer ones.
379
// TODO: This will coerce numbers larger than 31 bits to 0.
380
const timeoutMs = suspenseConfig.timeoutMs;
365
- updateLanePriority =
381
+ const transitionLanePriority =
382
timeoutMs === undefined || (timeoutMs | 0) < 10000
383
? TransitionShortLanePriority
384
: TransitionLongLanePriority;
385
+
386
+ if (currentEventPendingLanes !== NoLanes) {
387
+ currentEventPendingLanes =
388
+ mostRecentlyUpdatedRoot !== null
389
+ ? mostRecentlyUpdatedRoot.pendingLanes
390
+ : NoLanes;
391
+ }
392
+
393
+ lane = findTransitionLane(
394
+ transitionLanePriority,
395
+ currentEventWipLanes,
396
+ currentEventPendingLanes,
397
+ );
398
} else {
399
// TODO: If we're not inside `runWithPriority`, this returns the priority
400
// of the currently running task. That's probably not what we want.
372
- const priorityLevel = getCurrentPriorityLevel();
373
- switch (priorityLevel) {
374
- case ImmediateSchedulerPriority:
375
- updateLanePriority = SyncLanePriority;
376
- break;
377
- case UserBlockingSchedulerPriority:
378
- updateLanePriority = InputContinuousLanePriority;
379
- break;
380
- case NormalSchedulerPriority:
381
- case LowSchedulerPriority:
382
- // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
383
- updateLanePriority = DefaultLanePriority;
384
- break;
385
- case IdleSchedulerPriority:
386
- updateLanePriority = IdleLanePriority;
387
- break;
388
- default:
389
- invariant(false, 'Expected a valid priority level');
390
- }
391
- }
392
-
393
- // TODO: In the new system, what we'll do here is claim one of the bits of
394
- // the root's `pendingLanes` field, based on its priority. We'll combine this
395
- // function with `scheduleUpdateOnFiber` and `markRootUpdatedAtTime`.
396
- let expirationTime;
397
- switch (updateLanePriority) {
398
- case SyncLanePriority:
399
- return Sync;
400
- case SyncBatchedLanePriority:
401
- return Batched;
402
- case InputDiscreteLanePriority:
403
- case InputContinuousLanePriority:
404
- expirationTime = UserBlockingUpdateTime;
405
- break;
406
- case DefaultLanePriority:
407
- expirationTime = DefaultUpdateTime;
408
- break;
409
- case TransitionShortLanePriority:
410
- expirationTime = ShortTransition;
411
- break;
412
- case TransitionLongLanePriority:
413
- expirationTime = LongTransition;
414
- break;
415
- case HydrationContinuousLanePriority:
416
- expirationTime = ContinuousHydration;
417
- break;
418
- case IdleLanePriority:
419
- expirationTime = Idle;
420
- break;
421
- case OffscreenLanePriority:
422
- expirationTime = Never;
423
- break;
424
- default:
425
- invariant(false, 'Expected a valid priority level');
426
- }
401
+ const schedulerPriority = getCurrentPriorityLevel();
402
428
- // If we're in the middle of rendering a tree, do not update at the same
429
- // expiration time that is already rendering.
430
- // TODO: We shouldn't have to do this if the update is on a different root.
431
- // TODO: In the new system, we'll find a different bit that's not the one
432
- // we're currently rendering.
433
- if (
434
- workInProgressRoot !== null &&
435
- isSameExpirationTime(expirationTime, renderExpirationTime)
436
- ) {
437
- // This is a trick to move this update into a separate batch
438
- // TODO: This probably causes problems with ContinuousHydration and Idle
439
- expirationTime = bumpPriorityLower(expirationTime);
403
+ if (
404
+ // TODO: Temporary. We're removing the concept of discrete updates.
405
+ (executionContext & DiscreteEventContext) !== NoContext &&
406
+ schedulerPriority === UserBlockingSchedulerPriority
407
+ ) {
408
+ lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
409
+ } else {
410
+ const lanePriority = schedulerPriorityToLanePriority(schedulerPriority);
411
+ lane = findUpdateLane(lanePriority, currentEventWipLanes);
412
+ }
413
}
414
442
- return expirationTime;
415
+ return lane;
416
}
417
445
-export function scheduleUpdateOnFiber(
446
- fiber: Fiber,
447
- expirationTime: ExpirationTimeOpaque,
448
-) {
418
+export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane) {
419
checkForNestedUpdates();
420
warnAboutRenderPhaseUpdatesInDEV(fiber);
421
452
- const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
422
+ const root = markUpdateLaneFromFiberToRoot(fiber, lane);
423
if (root === null) {
424
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
425
return null;
429
// priority as an argument to that function and this one.
430
const priorityLevel = getCurrentPriorityLevel();
431
462
- if (isSameExpirationTime(expirationTime, (Sync: ExpirationTimeOpaque))) {
432
+ if (lane === SyncLane) {
433
if (
434
// Check if we're inside unbatchedUpdates
435
(executionContext & LegacyUnbatchedContext) !== NoContext &&
437
(executionContext & (RenderContext | CommitContext)) === NoContext
438
) {
439
// Register pending interactions on the root to avoid losing traced interaction data.
470
- schedulePendingInteractions(root, expirationTime);
440
+ schedulePendingInteractions(root, lane);
441
442
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
443
// root inside of batchedUpdates should be synchronous, but layout updates
445
performSyncWorkOnRoot(root);
446
} else {
447
ensureRootIsScheduled(root);
478
- schedulePendingInteractions(root, expirationTime);
448
+ schedulePendingInteractions(root, lane);
449
if (executionContext === NoContext) {
450
// Flush the synchronous work now, unless we're already working or inside
451
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
467
// This is the result of a discrete event. Track the lowest priority
468
// discrete update per root so we can flush them early, if needed.
469
if (rootsWithPendingDiscreteUpdates === null) {
500
- rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
470
+ rootsWithPendingDiscreteUpdates = new Set([root]);
471
} else {
502
- const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
503
- if (
504
- lastDiscreteTime === undefined ||
505
- !isSameOrHigherPriority(expirationTime, lastDiscreteTime)
506
- ) {
507
- rootsWithPendingDiscreteUpdates.set(root, expirationTime);
508
- }
472
+ rootsWithPendingDiscreteUpdates.add(root);
473
}
474
}
475
// Schedule other updates after in case the callback is sync.
476
ensureRootIsScheduled(root);
513
- schedulePendingInteractions(root, expirationTime);
477
+ schedulePendingInteractions(root, lane);
478
}
479
+
480
+ // We use this when assigning a lane for a transition inside
481
+ // `requestUpdateLane`. We assume it's the same as the root being updated,
482
+ // since in the common case of a single root app it probably is. If it's not
483
+ // the same root, then it's not a huge deal, we just might batch more stuff
484
+ // together more than necessary.
485
+ mostRecentlyUpdatedRoot = root;
486
}
487
488
// This is split into a separate function so we can mark a fiber with pending
489
// work without treating it as a typical update that originates from an event;
490
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
491
// on a fiber.
521
-function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
522
- // Update the source fiber's expiration time
523
- if (!isSameOrHigherPriority(fiber.expirationTime_opaque, expirationTime)) {
524
- fiber.expirationTime_opaque = expirationTime;
525
- }
492
+function markUpdateLaneFromFiberToRoot(
493
+ fiber: Fiber,
494
+ lane: Lane,
495
+): FiberRoot | null {
496
+ // Update the source fiber's lanes
497
+ fiber.lanes = mergeLanes(fiber.lanes, lane);
498
let alternate = fiber.alternate;
527
- if (
528
- alternate !== null &&
529
- !isSameOrHigherPriority(alternate.expirationTime_opaque, expirationTime)
530
- ) {
531
- alternate.expirationTime_opaque = expirationTime;
499
+ if (alternate !== null) {
500
+ alternate.lanes = mergeLanes(alternate.lanes, lane);
501
}
502
// Walk the parent path to the root and update the child expiration time.
503
let node = fiber.return;
507
} else {
508
while (node !== null) {
509
alternate = node.alternate;
541
- if (
542
- !isSameOrHigherPriority(node.childExpirationTime_opaque, expirationTime)
543
- ) {
544
- node.childExpirationTime_opaque = expirationTime;
545
- if (
546
- alternate !== null &&
547
- !isSameOrHigherPriority(
548
- alternate.childExpirationTime_opaque,
549
- expirationTime,
550
- )
551
- ) {
552
- alternate.childExpirationTime_opaque = expirationTime;
553
- }
554
- } else if (
555
- alternate !== null &&
556
- !isSameOrHigherPriority(
557
- alternate.childExpirationTime_opaque,
558
- expirationTime,
559
- )
560
- ) {
561
- alternate.childExpirationTime_opaque = expirationTime;
510
+ node.childLanes = mergeLanes(node.childLanes, lane);
511
+ if (alternate !== null) {
512
+ alternate.childLanes = mergeLanes(alternate.childLanes, lane);
513
}
514
if (node.return === null && node.tag === HostRoot) {
515
root = node.stateNode;
520
}
521
522
if (root !== null) {
523
+ // Mark that the root has a pending update.
524
+ markRootUpdated(root, lane);
525
if (workInProgressRoot === root) {
526
// Received an update to a tree that's in the middle of rendering. Mark
574
- // that's unprocessed work on this root.
575
- markUnprocessedUpdateTime(expirationTime);
576
-
527
+ // that there is unprocessed work on this root.
528
+ workInProgressRootUpdatedLanes = mergeLanes(
529
+ workInProgressRootUpdatedLanes,
530
+ lane,
531
+ );
532
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
533
// The root already suspended with a delay, which means this render
534
// definitely won't finish. Since we have a new update, let's mark it as
535
// suspended now, right before marking the incoming update. This has the
536
// effect of interrupting the current render and switching to the update.
582
- // TODO: This happens to work when receiving an update during the render
583
- // phase, because of the trick inside requestUpdateExpirationTime to
584
- // subtract 1 from `renderExpirationTime` to move it into a
585
- // separate bucket. But we should probably model it with an exception,
586
- // using the same mechanism we use to force hydration of a subtree.
587
- // TODO: This does not account for low pri updates that were already
588
- // scheduled before the root started rendering. Need to track the next
589
- // pending expiration time (perhaps by backtracking the return path) and
590
- // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
591
- markRootSuspendedAtTime(root, renderExpirationTime);
537
+ // TODO: Make sure this doesn't override pings that happen while we've
538
+ // already started rendering.
539
+ markRootSuspended(root, workInProgressRootRenderLanes);
540
}
541
}
594
- // Mark that the root has a pending update.
595
- markRootUpdatedAtTime(root, expirationTime);
542
}
543
544
return root;
545
}
546
601
-function getNextRootExpirationTimeToWorkOn(
602
- root: FiberRoot,
603
-): ExpirationTimeOpaque {
604
- // Determines the next expiration time that the root should render, taking
605
- // into account levels that may be suspended, or levels that may have
606
- // received a ping.
607
-
608
- const lastExpiredTime = root.lastExpiredTime_opaque;
609
- if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
610
- return lastExpiredTime;
611
- }
612
-
613
- // "Pending" refers to any update that hasn't committed yet, including if it
614
- // suspended. The "suspended" range is therefore a subset.
615
- const firstPendingTime = root.firstPendingTime_opaque;
616
- if (!isRootSuspendedAtTime(root, firstPendingTime)) {
617
- // The highest priority pending time is not suspended. Let's work on that.
618
- return firstPendingTime;
619
- }
620
-
621
- // If the first pending time is suspended, check if there's a lower priority
622
- // pending level that we know about. Or check if we received a ping. Work
623
- // on whichever is higher priority.
624
- const lastPingedTime = root.lastPingedTime_opaque;
625
- const nextKnownPendingLevel = root.nextKnownPendingLevel_opaque;
626
- const nextLevel = !isSameOrHigherPriority(
627
- nextKnownPendingLevel,
628
- lastPingedTime,
629
- )
630
- ? lastPingedTime
631
- : nextKnownPendingLevel;
632
- if (
633
- isSameOrHigherPriority((Idle: ExpirationTimeOpaque), nextLevel) &&
634
- !isSameExpirationTime(firstPendingTime, nextLevel)
635
- ) {
636
- // Don't work on Idle/Never priority unless everything else is committed.
637
- return NoWork;
638
- }
639
- return nextLevel;
640
-}
641
-
547
// Use this function to schedule a task for a root. There's only one task per
548
// root; if a task was already scheduled, we'll check to make sure the
549
// expiration time of the existing task is the same as the expiration time of
552
function ensureRootIsScheduled(root: FiberRoot) {
553
const existingCallbackNode = root.callbackNode;
554
650
- const newCallbackId = getNextRootExpirationTimeToWorkOn(root);
651
- if (newCallbackId === (NoWork: ExpirationTimeOpaque)) {
555
+ const newCallbackId = getNextLanes(
556
+ root,
557
+ root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
558
+ );
559
+ // This returns the priority level computed during the `getNextLanes` call.
560
+ const newCallbackPriorityLevel = returnNextLanesPriority();
561
+
562
+ if (newCallbackId === NoLanes) {
563
// Special case: There's nothing to work on.
564
if (existingCallbackNode !== null) {
565
cancelCallback(existingCallbackNode);
566
root.expiresAt = -1;
567
root.callbackNode = null;
568
root.callbackIsSync = false;
658
- root.callbackId = NoWork;
569
+ root.callbackId = NoLanes;
570
}
571
return;
572
}
573
663
- const newTaskIsSync =
664
- newCallbackId === (Sync: ExpirationTimeOpaque) ||
665
- !isSameExpirationTime(
666
- root.lastExpiredTime_opaque,
667
- (NoWork: ExpirationTimeOpaque),
668
- );
574
+ const newTaskIsSync = newCallbackPriorityLevel === SyncLanePriority;
575
576
// Check if there's an existing task. We may be able to reuse it.
577
const existingTaskId = root.callbackId;
578
const existingCallbackIsSync = root.callbackIsSync;
673
- if (existingTaskId !== (NoWork: ExpirationTimeOpaque)) {
579
+ if (existingTaskId !== NoLanes) {
580
if (newCallbackId === existingTaskId) {
581
// This task is already scheduled. Let's check its priority.
582
if (
595
// Schedule a new callback.
596
let newCallbackNode;
597
if (newTaskIsSync) {
692
- // Special case: Sync React callbacks are scheduled on a special internal queue
598
+ // Special case: Sync React callbacks are scheduled on a special
599
+ // internal queue
600
newCallbackNode = scheduleSyncCallback(
601
performSyncWorkOnRoot.bind(null, root),
602
);
603
} else {
697
- // TODO: Use LanePriority instead of SchedulerPriority
698
- const priorityLevel = inferPriorityFromExpirationTime(newCallbackId);
699
- if (
700
- priorityLevel === NormalSchedulerPriority ||
701
- priorityLevel === UserBlockingSchedulerPriority
702
- ) {
703
- const existingExpirationTime = root.expiresAt;
704
- const currentTimeMs = now();
705
-
706
- // Compute an expiration time based on the priority level.
707
- const expiration =
708
- priorityLevel === UserBlockingSchedulerPriority ? 250 : 5000;
709
-
710
- let msUntilExpiration;
711
- if (existingExpirationTime === -1) {
712
- // This is the first concurrent update on the root. Use the expiration
713
- // time we just computed.
714
- msUntilExpiration = expiration;
715
- root.expiresAt = msUntilExpiration + currentTimeMs;
716
- } else {
717
- // There's already an expiration time. Use the smaller of the current
718
- // expiration and the one we just computed.
719
- msUntilExpiration = existingExpirationTime - currentTimeMs;
720
- if (expiration < msUntilExpiration) {
721
- root.expiresAt = expiration;
722
- }
723
- }
724
- newCallbackNode = scheduleCallback(
725
- priorityLevel,
726
- performConcurrentWorkOnRoot.bind(null, root),
727
- );
728
- } else {
729
- newCallbackNode = scheduleCallback(
730
- priorityLevel,
731
- performConcurrentWorkOnRoot.bind(null, root),
732
- );
733
- }
604
+ const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
605
+ newCallbackPriorityLevel,
606
+ );
607
+ newCallbackNode = scheduleCallback(
608
+ schedulerPriorityLevel,
609
+ performConcurrentWorkOnRoot.bind(null, root),
610
+ );
611
}
612
613
root.callbackId = newCallbackId;
621
// Since we know we're in a React event, we can clear the current
622
// event time. The next update will compute a new event time.
623
currentEventTime = -1;
624
+ currentEventWipLanes = NoLanes;
625
+ currentEventPendingLanes = NoLanes;
626
627
// Determine the next expiration time to work on, using the fields stored
628
// on the root.
750
- let expirationTime = getNextRootExpirationTimeToWorkOn(root);
751
- if (isSameExpirationTime(expirationTime, (NoWork: ExpirationTimeOpaque))) {
629
+ let lanes = getNextLanes(
630
+ root,
631
+ root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
632
+ );
633
+ if (lanes === NoLanes) {
634
return null;
635
}
636
637
+ // Check if any work has expired.
638
+ const rootExpiresAt = root.expiresAt;
639
+ if (rootExpiresAt !== -1 && rootExpiresAt < now()) {
640
+ // Something expired. Flush synchronously until there's no expired
641
+ // work left.
642
+ // TODO: Should flush only the lanes that have expired, and maybe any lanes
643
+ // that are higher priority than that.
644
+ markRootExpired(root, lanes);
645
+ // This will schedule a synchronous callback.
646
+ ensureRootIsScheduled(root);
647
+ return null;
648
+ }
649
+ // Similar branch, but for Scheduler.
650
+ // TODO: This is only here to account for a Scheduler bug where `shouldYield`
651
+ // sometimes returns `true` even if `didTimeout` is true, which leads to
652
+ // an infinite loop. Once the bug in Scheduler is fixed, we can remove this,
653
+ // since we track expiration times ourselves.
654
if (didTimeout) {
756
- // The render task took too long to complete. Mark the root as expired to
655
+ // The Scheduler task took too long to complete. Mark the root as expired to
656
// prevent yielding to other tasks until this one finishes.
758
- markRootExpiredAtTime(root, expirationTime);
657
+ markRootExpired(root, lanes);
658
// This will schedule a synchronous callback.
659
ensureRootIsScheduled(root);
660
return null;
668
669
flushPassiveEffects();
670
772
- let exitStatus = renderRootConcurrent(root, expirationTime);
671
+ let exitStatus = renderRootConcurrent(root, lanes);
672
774
- if (exitStatus !== RootIncomplete) {
673
+ if (
674
+ includesSomeLane(
675
+ workInProgressRootIncludedLanes,
676
+ workInProgressRootUpdatedLanes,
677
+ )
678
+ ) {
679
+ // The render included lanes that were updated during the render phase.
680
+ // For example, when unhiding a hidden tree, we include all the lanes
681
+ // that were previously skipped when the tree was hidden. That set of
682
+ // lanes is a superset of the lanes we started rendering with.
683
+ //
684
+ // So we'll throw out the current work and restart.
685
+ prepareFreshStack(root, NoLanes);
686
+ } else if (exitStatus !== RootIncomplete) {
687
if (exitStatus === RootErrored) {
776
- // If something threw an error, try rendering one more time. We'll
777
- // render synchronously to block concurrent data mutations, and we'll
778
- // render at Idle (or lower) so that all pending updates are included.
779
- // If it still fails after the second attempt, we'll give up and commit
780
- // the resulting tree.
781
- expirationTime = !isSameOrHigherPriority(
782
- (Idle: ExpirationTimeOpaque),
783
- expirationTime,
784
- )
785
- ? (Idle: ExpirationTimeOpaque)
786
- : expirationTime;
787
- exitStatus = renderRootSync(root, expirationTime);
688
+ // If something threw an error, try rendering one more time. We'll render
689
+ // synchronously to block concurrent data mutations, and we'll includes
690
+ // all pending updates are included. If it still fails after the second
691
+ // attempt, we'll give up and commit the resulting tree.
692
+ lanes = getLanesToRetrySynchronouslyOnError(root);
693
+ if (lanes !== NoLanes) {
694
+ exitStatus = renderRootSync(root, lanes);
695
+ }
696
}
697
698
if (exitStatus === RootFatalErrored) {
699
const fatalError = workInProgressRootFatalError;
792
- prepareFreshStack(root, expirationTime);
793
- markRootSuspendedAtTime(root, expirationTime);
700
+ prepareFreshStack(root, NoLanes);
701
+ markRootSuspended(root, lanes);
702
ensureRootIsScheduled(root);
703
throw fatalError;
704
}
707
// or, if something suspended, wait to commit it after a timeout.
708
const finishedWork: Fiber = (root.current.alternate: any);
709
root.finishedWork = finishedWork;
802
- root.finishedExpirationTime_opaque = expirationTime;
803
- root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(
804
- finishedWork,
805
- );
806
- finishConcurrentRender(root, finishedWork, exitStatus, expirationTime);
710
+ root.finishedLanes = lanes;
711
+ finishConcurrentRender(root, finishedWork, exitStatus, lanes);
712
}
713
714
ensureRootIsScheduled(root);
720
return null;
721
}
722
818
-function finishConcurrentRender(
819
- root,
820
- finishedWork,
821
- exitStatus,
822
- expirationTime,
823
-) {
723
+function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
724
switch (exitStatus) {
725
case RootIncomplete:
726
case RootFatalErrored: {
736
break;
737
}
738
case RootSuspended: {
839
- markRootSuspendedAtTime(root, expirationTime);
840
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
739
+ markRootSuspended(root, lanes);
740
741
// We have an acceptable loading state. We need to figure out if we
742
// should immediately commit it or wait a bit.
761
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
762
// Don't bother with a very short suspense time.
763
if (msUntilTimeout > 10) {
865
- if (workInProgressRootHasPendingPing) {
866
- const lastPingedTime = root.lastPingedTime_opaque;
867
- if (
868
- isSameExpirationTime(
869
- lastPingedTime,
870
- (NoWork: ExpirationTimeOpaque),
871
- ) ||
872
- isSameOrHigherPriority(lastPingedTime, expirationTime)
873
- ) {
874
- // This render was pinged but we didn't get to restart
875
- // earlier so try restarting now instead.
876
- root.lastPingedTime_opaque = expirationTime;
877
- prepareFreshStack(root, expirationTime);
878
- break;
879
- }
880
- }
881
-
882
- const nextTime = getNextRootExpirationTimeToWorkOn(root);
883
- if (
884
- !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
885
- !isSameExpirationTime(nextTime, expirationTime)
886
- ) {
764
+ const nextLanes = getNextLanes(root, NoLanes);
765
+ if (nextLanes !== NoLanes) {
766
// There's additional work on this root.
767
break;
768
}
890
- if (
891
- !isSameExpirationTime(
892
- lastSuspendedTime,
893
- (NoWork: ExpirationTimeOpaque),
894
- ) &&
895
- !isSameExpirationTime(lastSuspendedTime, expirationTime)
896
- ) {
769
+ const suspendedLanes = root.suspendedLanes;
770
+ if (!isSubsetOfLanes(suspendedLanes, lanes)) {
771
// We should prefer to render the fallback of at the last
772
// suspended level. Ping the last suspended level to try
773
// rendering it again.
900
- root.lastPingedTime_opaque = lastSuspendedTime;
774
+ // FIXME: What if the suspended lanes are Idle? Should not restart.
775
+ markRootPinged(root, suspendedLanes);
776
break;
777
}
778
791
break;
792
}
793
case RootSuspendedWithDelay: {
919
- markRootSuspendedAtTime(root, expirationTime);
920
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
794
+ markRootSuspended(root, lanes);
795
796
if (
797
// do not delay if we're inside an act() scope
799
) {
800
// We're suspended in a state that should be avoided. We'll try to
801
// avoid committing it for as long as the timeouts let us.
928
- if (workInProgressRootHasPendingPing) {
929
- const lastPingedTime = root.lastPingedTime_opaque;
930
- if (
931
- isSameExpirationTime(
932
- lastPingedTime,
933
- (NoWork: ExpirationTimeOpaque),
934
- ) ||
935
- isSameOrHigherPriority(lastPingedTime, expirationTime)
936
- ) {
937
- // This render was pinged but we didn't get to restart earlier
938
- // so try restarting now instead.
939
- root.lastPingedTime_opaque = expirationTime;
940
- prepareFreshStack(root, expirationTime);
941
- break;
942
- }
943
- }
944
-
945
- const nextTime = getNextRootExpirationTimeToWorkOn(root);
946
- if (
947
- !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
948
- !isSameExpirationTime(nextTime, expirationTime)
949
- ) {
802
+ const nextLanes = getNextLanes(root, NoLanes);
803
+ if (nextLanes !== NoLanes) {
804
// There's additional work on this root.
805
break;
806
}
953
- if (
954
- !isSameExpirationTime(
955
- lastSuspendedTime,
956
- (NoWork: ExpirationTimeOpaque),
957
- ) &&
958
- !isSameExpirationTime(lastSuspendedTime, expirationTime)
959
- ) {
807
+ const suspendedLanes = root.suspendedLanes;
808
+ if (!isSubsetOfLanes(suspendedLanes, lanes)) {
809
// We should prefer to render the fallback of at the last
810
// suspended level. Ping the last suspended level to try
811
// rendering it again.
963
- root.lastPingedTime_opaque = lastSuspendedTime;
812
+ // FIXME: What if the suspended lanes are Idle? Should not restart.
813
+ markRootPinged(root, suspendedLanes);
814
break;
815
}
816
865
workInProgressRootCanSuspendUsingConfig,
866
);
867
if (msUntilTimeout > 10) {
1018
- markRootSuspendedAtTime(root, expirationTime);
868
+ markRootSuspended(root, lanes);
869
root.timeoutHandle = scheduleTimeout(
870
commitRoot.bind(null, root),
871
msUntilTimeout,
882
}
883
}
884
885
+function markRootSuspended(root, suspendedLanes) {
886
+ // When suspending, we should always exclude lanes that were pinged or (more
887
+ // rarely, since we try to avoid it) updated during the render phase.
888
+ // TODO: Lol maybe there's a better way to factor this besides this
889
+ // obnoxiously named function :)
890
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
891
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootUpdatedLanes);
892
+ markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes);
893
+}
894
+
895
// This is the entry point for synchronous tasks that don't go
896
// through Scheduler
897
function performSyncWorkOnRoot(root) {
902
903
flushPassiveEffects();
904
1045
- const lastExpiredTime = root.lastExpiredTime_opaque;
1046
-
1047
- let expirationTime;
1048
- if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
1049
- // There's expired work on this root. Check if we have a partial tree
1050
- // that we can reuse.
905
+ let lanes;
906
+ let exitStatus;
907
+ if (
908
+ root === workInProgressRoot &&
909
+ includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
910
+ ) {
911
+ // There's a partial tree, and at least one of its lanes has expired. Finish
912
+ // rendering it before rendering the rest of the expired work.
913
+ lanes = workInProgressRootRenderLanes;
914
+ exitStatus = renderRootSync(root, lanes);
915
if (
1052
- root === workInProgressRoot &&
1053
- isSameOrHigherPriority(renderExpirationTime, lastExpiredTime)
916
+ includesSomeLane(
917
+ workInProgressRootIncludedLanes,
918
+ workInProgressRootUpdatedLanes,
919
+ )
920
) {
1055
- // There's a partial tree with equal or greater than priority than the
1056
- // expired level. Finish rendering it before rendering the rest of the
1057
- // expired work.
1058
- expirationTime = renderExpirationTime;
1059
- } else {
1060
- // Start a fresh tree.
1061
- expirationTime = lastExpiredTime;
921
+ // The render included lanes that were updated during the render phase.
922
+ // For example, when unhiding a hidden tree, we include all the lanes
923
+ // that were previously skipped when the tree was hidden. That set of
924
+ // lanes is a superset of the lanes we started rendering with.
925
+ //
926
+ // Note that this only happens when part of the tree is rendered
927
+ // concurrently. If the whole tree is rendered synchronously, then there
928
+ // are no interleaved events.
929
+ lanes = getNextLanes(root, lanes);
930
+ exitStatus = renderRootSync(root, lanes);
931
}
932
} else {
1064
- // There's no expired work. This must be a new, synchronous render.
1065
- expirationTime = Sync;
933
+ lanes = getNextLanes(root, NoLanes);
934
+ exitStatus = renderRootSync(root, lanes);
935
}
936
1068
- let exitStatus = renderRootSync(root, expirationTime);
1069
-
937
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
1071
- // If something threw an error, try rendering one more time. We'll
1072
- // render synchronously to block concurrent data mutations, and we'll
1073
- // render at Idle (or lower) so that all pending updates are included.
1074
- // If it still fails after the second attempt, we'll give up and commit
1075
- // the resulting tree.
1076
- expirationTime = !isSameOrHigherPriority(
1077
- (Idle: ExpirationTimeOpaque),
1078
- expirationTime,
1079
- )
1080
- ? (Idle: ExpirationTimeOpaque)
1081
- : expirationTime;
1082
- exitStatus = renderRootSync(root, expirationTime);
938
+ // If something threw an error, try rendering one more time. We'll render
939
+ // synchronously to block concurrent data mutations, and we'll includes
940
+ // all pending updates are included. If it still fails after the second
941
+ // attempt, we'll give up and commit the resulting tree.
942
+ lanes = getLanesToRetrySynchronouslyOnError(root);
943
+ if (lanes !== NoLanes) {
944
+ exitStatus = renderRootSync(root, lanes);
945
+ }
946
}
947
948
if (exitStatus === RootFatalErrored) {
949
const fatalError = workInProgressRootFatalError;
1087
- prepareFreshStack(root, expirationTime);
1088
- markRootSuspendedAtTime(root, expirationTime);
950
+ prepareFreshStack(root, NoLanes);
951
+ markRootSuspended(root, lanes);
952
ensureRootIsScheduled(root);
953
throw fatalError;
954
}
957
// will commit it even if something suspended.
958
const finishedWork: Fiber = (root.current.alternate: any);
959
root.finishedWork = finishedWork;
1097
- root.finishedExpirationTime_opaque = expirationTime;
1098
- root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(finishedWork);
960
+ root.finishedLanes = lanes;
961
commitRoot(root);
962
963
// Before exiting, make sure there's a callback scheduled for the next
967
return null;
968
}
969
1108
-export function flushRoot(
1109
- root: FiberRoot,
1110
- expirationTime: ExpirationTimeOpaque,
1111
-) {
1112
- markRootExpiredAtTime(root, expirationTime);
970
+export function flushRoot(root: FiberRoot, lanes: Lanes) {
971
+ markRootExpired(root, lanes);
972
ensureRootIsScheduled(root);
973
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
974
flushSyncCallbackQueue();
1023
// immediately flush them.
1024
const roots = rootsWithPendingDiscreteUpdates;
1025
rootsWithPendingDiscreteUpdates = null;
1167
- roots.forEach((expirationTime, root) => {
1168
- markRootExpiredAtTime(root, expirationTime);
1026
+ roots.forEach(root => {
1027
+ markDiscreteUpdatesExpired(root);
1028
ensureRootIsScheduled(root);
1029
});
1030
}
1137
}
1138
}
1139
1281
-export function pushRenderExpirationTime(
1282
- fiber: Fiber,
1283
- subtreeRenderTime: ExpirationTimeOpaque,
1284
-) {
1285
- pushToStack(renderExpirationTimeCursor, renderExpirationTime, fiber);
1286
- renderExpirationTime = subtreeRenderTime;
1140
+export function pushRenderLanes(fiber: Fiber, lanes: Lanes) {
1141
+ pushToStack(subtreeRenderLanesCursor, subtreeRenderLanes, fiber);
1142
+ subtreeRenderLanes = mergeLanes(subtreeRenderLanes, lanes);
1143
+ workInProgressRootIncludedLanes = mergeLanes(
1144
+ workInProgressRootIncludedLanes,
1145
+ lanes,
1146
+ );
1147
}
1148
1289
-export function popRenderExpirationTime(fiber: Fiber) {
1290
- renderExpirationTime = renderExpirationTimeCursor.current;
1291
- popFromStack(renderExpirationTimeCursor, fiber);
1149
+export function popRenderLanes(fiber: Fiber) {
1150
+ subtreeRenderLanes = subtreeRenderLanesCursor.current;
1151
+ popFromStack(subtreeRenderLanesCursor, fiber);
1152
}
1153
1294
-function prepareFreshStack(root, expirationTime) {
1154
+function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1155
root.finishedWork = null;
1296
- root.finishedExpirationTime_opaque = NoWork;
1156
+ root.finishedLanes = NoLanes;
1157
1158
const timeoutHandle = root.timeoutHandle;
1159
if (timeoutHandle !== noTimeout) {
1164
cancelTimeout(timeoutHandle);
1165
}
1166
1307
- // Check if there's a suspended level at lower priority.
1308
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
1309
- if (
1310
- !isSameExpirationTime(lastSuspendedTime, (NoWork: ExpirationTimeOpaque)) &&
1311
- !isSameOrHigherPriority(lastSuspendedTime, expirationTime)
1312
- ) {
1313
- const lastPingedTime = root.lastPingedTime_opaque;
1314
- // Make sure the suspended level is marked as pinged so that we return back
1315
- // to it later, in case the render we're about to start gets aborted.
1316
- // Generally we only reach this path via a ping, but we shouldn't assume
1317
- // that will always be the case.
1318
- // Note: This is defensive coding to prevent a pending commit from
1319
- // being dropped without being rescheduled. It shouldn't be necessary.
1320
- if (
1321
- isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) ||
1322
- !isSameOrHigherPriority(lastSuspendedTime, lastPingedTime)
1323
- ) {
1324
- root.lastPingedTime_opaque = lastSuspendedTime;
1325
- }
1326
- }
1327
-
1167
if (workInProgress !== null) {
1168
let interruptedWork = workInProgress.return;
1169
while (interruptedWork !== null) {
1173
}
1174
workInProgressRoot = root;
1175
workInProgress = createWorkInProgress(root.current, null);
1337
- renderExpirationTime = expirationTime;
1176
+ workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1177
workInProgressRootExitStatus = RootIncomplete;
1178
workInProgressRootFatalError = null;
1179
workInProgressRootLatestProcessedEventTime = -1;
1180
workInProgressRootLatestSuspenseTimeout = -1;
1181
workInProgressRootCanSuspendUsingConfig = null;
1343
- workInProgressRootNextUnprocessedUpdateTime = NoWork;
1344
- workInProgressRootHasPendingPing = false;
1182
+ workInProgressRootSkippedLanes = NoLanes;
1183
+ workInProgressRootUpdatedLanes = NoLanes;
1184
+ workInProgressRootPingedLanes = NoLanes;
1185
1186
if (enableSchedulerTracing) {
1187
spawnedWorkDuringRender = null;
1233
erroredWork.return,
1234
erroredWork,
1235
thrownValue,
1396
- renderExpirationTime,
1236
+ workInProgressRootRenderLanes,
1237
);
1238
completeUnitOfWork(erroredWork);
1239
} catch (yetAnotherThrownValue) {
1319
}
1320
}
1321
1482
-export function markUnprocessedUpdateTime(
1483
- expirationTime: ExpirationTimeOpaque,
1484
-): void {
1485
- if (
1486
- !isSameOrHigherPriority(
1487
- workInProgressRootNextUnprocessedUpdateTime,
1488
- expirationTime,
1489
- )
1490
- ) {
1491
- workInProgressRootNextUnprocessedUpdateTime = expirationTime;
1492
- }
1322
+export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
1323
+ workInProgressRootSkippedLanes = mergeLanes(
1324
+ lane,
1325
+ workInProgressRootSkippedLanes,
1326
+ );
1327
}
1328
1329
export function renderDidSuspend(): void {
1340
workInProgressRootExitStatus = RootSuspendedWithDelay;
1341
}
1342
1509
- // Check if there's a lower priority update somewhere else in the tree.
1343
+ // Check if there are updates that we skipped tree that might have unblocked
1344
+ // this render.
1345
if (
1511
- !isSameExpirationTime(
1512
- workInProgressRootNextUnprocessedUpdateTime,
1513
- (NoWork: ExpirationTimeOpaque),
1514
- ) &&
1515
- workInProgressRoot !== null
1346
+ workInProgressRoot !== null &&
1347
+ (hasUpdatePriority(workInProgressRootSkippedLanes) ||
1348
+ hasUpdatePriority(workInProgressRootUpdatedLanes))
1349
) {
1517
- // Mark the current render as suspended, and then mark that there's a
1518
- // pending update.
1519
- // TODO: This should immediately interrupt the current render, instead
1520
- // of waiting until the next time we yield.
1521
- markRootSuspendedAtTime(workInProgressRoot, renderExpirationTime);
1522
- markRootUpdatedAtTime(
1523
- workInProgressRoot,
1524
- workInProgressRootNextUnprocessedUpdateTime,
1525
- );
1350
+ // Mark the current render as suspended so that we switch to working on
1351
+ // the updates that were skipped. Usually we only suspend at the end of
1352
+ // the render phase.
1353
+ // TODO: We should probably always mark the root as suspended immediately
1354
+ // (inside this function), since by suspending at the end of the render
1355
+ // phase introduces a potential mistake where we suspend lanes that were
1356
+ // pinged or updated while we were rendering.
1357
+ markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes);
1358
}
1359
}
1360
1372
return workInProgressRootExitStatus === RootIncomplete;
1373
}
1374
1543
-function renderRootSync(root, expirationTime) {
1375
+function renderRootSync(root: FiberRoot, lanes: Lanes) {
1376
const prevExecutionContext = executionContext;
1377
executionContext |= RenderContext;
1378
const prevDispatcher = pushDispatcher(root);
1379
1548
- // If the root or expiration time have changed, throw out the existing stack
1380
+ // If the root or lanes have changed, throw out the existing stack
1381
// and prepare a fresh one. Otherwise we'll continue where we left off.
1550
- if (
1551
- root !== workInProgressRoot ||
1552
- !isSameExpirationTime(expirationTime, renderExpirationTime)
1553
- ) {
1554
- prepareFreshStack(root, expirationTime);
1555
- startWorkOnPendingInteractions(root, expirationTime);
1382
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1383
+ prepareFreshStack(root, lanes);
1384
+ startWorkOnPendingInteractions(root, lanes);
1385
}
1386
1387
const prevInteractions = pushInteractions(root);
1413
1414
// Set this to null to indicate there's no in-progress render.
1415
workInProgressRoot = null;
1416
+ workInProgressRootRenderLanes = NoLanes;
1417
1418
return workInProgressRootExitStatus;
1419
}
1427
}
1428
}
1429
1600
-function renderRootConcurrent(root, expirationTime) {
1430
+function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
1431
const prevExecutionContext = executionContext;
1432
executionContext |= RenderContext;
1433
const prevDispatcher = pushDispatcher(root);
1434
1605
- // If the root or expiration time have changed, throw out the existing stack
1435
+ // If the root or lanes have changed, throw out the existing stack
1436
// and prepare a fresh one. Otherwise we'll continue where we left off.
1607
- if (
1608
- root !== workInProgressRoot ||
1609
- !isSameExpirationTime(expirationTime, renderExpirationTime)
1610
- ) {
1611
- prepareFreshStack(root, expirationTime);
1612
- startWorkOnPendingInteractions(root, expirationTime);
1437
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1438
+ prepareFreshStack(root, lanes);
1439
+ startWorkOnPendingInteractions(root, lanes);
1440
}
1441
1442
const prevInteractions = pushInteractions(root);
1465
// Completed the tree.
1466
// Set this to null to indicate there's no in-progress render.
1467
workInProgressRoot = null;
1468
+ workInProgressRootRenderLanes = NoLanes;
1469
1470
// Return the final exit status.
1471
return workInProgressRootExitStatus;
1490
let next;
1491
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
1492
startProfilerTimer(unitOfWork);
1665
- next = beginWork(current, unitOfWork, renderExpirationTime);
1493
+ next = beginWork(current, unitOfWork, subtreeRenderLanes);
1494
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
1495
} else {
1668
- next = beginWork(current, unitOfWork, renderExpirationTime);
1496
+ next = beginWork(current, unitOfWork, subtreeRenderLanes);
1497
}
1498
1499
resetCurrentDebugFiberInDEV();
1527
!enableProfilerTimer ||
1528
(completedWork.mode & ProfileMode) === NoMode
1529
) {
1702
- next = completeWork(current, completedWork, renderExpirationTime);
1530
+ next = completeWork(current, completedWork, subtreeRenderLanes);
1531
} else {
1532
startProfilerTimer(completedWork);
1705
- next = completeWork(current, completedWork, renderExpirationTime);
1533
+ next = completeWork(current, completedWork, subtreeRenderLanes);
1534
// Update render duration assuming we didn't error.
1535
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1536
}
1537
resetCurrentDebugFiberInDEV();
1710
- resetChildExpirationTime(completedWork);
1538
+ resetChildLanes(completedWork);
1539
1540
if (next !== null) {
1541
// Completing this fiber spawned new work. Work on that next.
1585
// This fiber did not complete because something threw. Pop values off
1586
// the stack without entering the complete phase. If this is a boundary,
1587
// capture values if possible.
1760
- const next = unwindWork(completedWork, renderExpirationTime);
1588
+ const next = unwindWork(completedWork, subtreeRenderLanes);
1589
1590
// Because this fiber did not complete, don't reset its expiration time.
1591
1641
}
1642
}
1643
1816
-function getRemainingExpirationTime(fiber: Fiber) {
1817
- const updateExpirationTime = fiber.expirationTime_opaque;
1818
- const childExpirationTime = fiber.childExpirationTime_opaque;
1819
- return !isSameOrHigherPriority(childExpirationTime, updateExpirationTime)
1820
- ? updateExpirationTime
1821
- : childExpirationTime;
1822
-}
1823
-
1824
-function resetChildExpirationTime(completedWork: Fiber) {
1644
+function resetChildLanes(completedWork: Fiber) {
1645
if (
1826
- !isSameExpirationTime(
1827
- renderExpirationTime,
1828
- (Never: ExpirationTimeOpaque),
1829
- ) &&
1830
- isSameExpirationTime(
1831
- completedWork.childExpirationTime_opaque,
1832
- (Never: ExpirationTimeOpaque),
1833
- )
1646
+ // TODO: Move this check out of the hot path by moving `resetChildLanes`
1647
+ // to switch statement in `completeWork`.
1648
+ (completedWork.tag === LegacyHiddenComponent ||
1649
+ completedWork.tag === OffscreenComponent) &&
1650
+ completedWork.memoizedState !== null &&
1651
+ !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))
1652
) {
1653
// The children of this component are hidden. Don't bubble their
1654
// expiration times.
1655
return;
1656
}
1657
1840
- let newChildExpirationTime = NoWork;
1658
+ let newChildLanes = NoLanes;
1659
1660
// Bubble up the earliest expiration time.
1661
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
1677
1678
let child = completedWork.child;
1679
while (child !== null) {
1862
- const childUpdateExpirationTime = child.expirationTime_opaque;
1863
- const childChildExpirationTime = child.childExpirationTime_opaque;
1864
- if (
1865
- !isSameOrHigherPriority(
1866
- newChildExpirationTime,
1867
- childUpdateExpirationTime,
1868
- )
1869
- ) {
1870
- newChildExpirationTime = childUpdateExpirationTime;
1871
- }
1872
- if (
1873
- !isSameOrHigherPriority(
1874
- newChildExpirationTime,
1875
- childChildExpirationTime,
1876
- )
1877
- ) {
1878
- newChildExpirationTime = childChildExpirationTime;
1879
- }
1680
+ newChildLanes = mergeLanes(
1681
+ newChildLanes,
1682
+ mergeLanes(child.lanes, child.childLanes),
1683
+ );
1684
if (shouldBubbleActualDurations) {
1685
actualDuration += child.actualDuration;
1686
}
1692
} else {
1693
let child = completedWork.child;
1694
while (child !== null) {
1891
- const childUpdateExpirationTime = child.expirationTime_opaque;
1892
- const childChildExpirationTime = child.childExpirationTime_opaque;
1893
- if (
1894
- !isSameOrHigherPriority(
1895
- newChildExpirationTime,
1896
- childUpdateExpirationTime,
1897
- )
1898
- ) {
1899
- newChildExpirationTime = childUpdateExpirationTime;
1900
- }
1901
- if (
1902
- !isSameOrHigherPriority(
1903
- newChildExpirationTime,
1904
- childChildExpirationTime,
1905
- )
1906
- ) {
1907
- newChildExpirationTime = childChildExpirationTime;
1908
- }
1695
+ newChildLanes = mergeLanes(
1696
+ newChildLanes,
1697
+ mergeLanes(child.lanes, child.childLanes),
1698
+ );
1699
child = child.sibling;
1700
}
1701
}
1702
1913
- completedWork.childExpirationTime_opaque = newChildExpirationTime;
1703
+ completedWork.childLanes = newChildLanes;
1704
}
1705
1706
function commitRoot(root) {
1730
);
1731
1732
const finishedWork = root.finishedWork;
1943
- const expirationTime = root.finishedExpirationTime_opaque;
1733
+ const lanes = root.finishedLanes;
1734
if (finishedWork === null) {
1735
return null;
1736
}
1737
root.finishedWork = null;
1948
- root.finishedExpirationTime_opaque = NoWork;
1738
+ root.finishedLanes = NoLanes;
1739
1740
invariant(
1741
finishedWork !== root.current,
1746
// commitRoot never returns a continuation; it always finishes synchronously.
1747
// So we can clear these now to allow a new callback to be scheduled.
1748
root.callbackNode = null;
1959
- root.callbackId = NoWork;
1749
+ root.callbackId = NoLanes;
1750
// TODO: Use LanePriority instead of SchedulerPriority
1751
if (renderPriorityLevel < ImmediateSchedulerPriority) {
1752
// If this was a concurrent render, we can reset the expiration time.
1755
1756
// Update the first and last pending times on this root. The new first
1757
// pending time is whatever is left on the root fiber.
1968
- const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
1969
- finishedWork,
1970
- );
1971
- markRootFinishedAtTime(
1972
- root,
1973
- expirationTime,
1974
- remainingExpirationTimeBeforeCommit,
1975
- );
1758
+ let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
1759
+ markRootFinished(root, remainingLanes);
1760
1761
// Clear already finished discrete updates in case that a later call of
1762
// `flushDiscreteUpdates` starts a useless render pass which may cancels
1763
// a scheduled timeout.
1764
if (rootsWithPendingDiscreteUpdates !== null) {
1981
- const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
1765
if (
1983
- lastDiscreteTime !== undefined &&
1984
- !isSameOrHigherPriority(
1985
- remainingExpirationTimeBeforeCommit,
1986
- lastDiscreteTime,
1987
- )
1766
+ !hasDiscreteLanes(remainingLanes) &&
1767
+ rootsWithPendingDiscreteUpdates.has(root)
1768
) {
1769
rootsWithPendingDiscreteUpdates.delete(root);
1770
}
1774
// We can reset these now that they are finished.
1775
workInProgressRoot = null;
1776
workInProgress = null;
1997
- renderExpirationTime = NoWork;
1777
+ workInProgressRootRenderLanes = NoLanes;
1778
} else {
1779
// This indicates that the last root we worked on is not the same one that
1780
// we're committing now. This most commonly happens when a suspended root
1892
nextEffect = firstEffect;
1893
do {
1894
if (__DEV__) {
2115
- invokeGuardedCallback(
2116
- null,
2117
- commitLayoutEffects,
2118
- null,
2119
- root,
2120
- expirationTime,
2121
- );
1895
+ invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
1896
if (hasCaughtError()) {
1897
invariant(nextEffect !== null, 'Should be working on an effect.');
1898
const error = clearCaughtError();
1901
}
1902
} else {
1903
try {
2130
- commitLayoutEffects(root, expirationTime);
1904
+ commitLayoutEffects(root, lanes);
1905
} catch (error) {
1906
invariant(nextEffect !== null, 'Should be working on an effect.');
1907
captureCommitPhaseError(nextEffect, error);
1938
// schedule a callback until after flushing layout work.
1939
rootDoesHavePassiveEffects = false;
1940
rootWithPendingPassiveEffects = root;
2167
- pendingPassiveEffectsExpirationTime = expirationTime;
1941
+ pendingPassiveEffectsLanes = lanes;
1942
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
1943
} else {
1944
// We are done with the effect chain at this point so let's clear the
1952
}
1953
}
1954
1955
+ // Read this again, since an effect might have updated it
1956
+ remainingLanes = root.pendingLanes;
1957
+
1958
// Check if there's remaining work on this root
2182
- const remainingExpirationTime = root.firstPendingTime_opaque;
2183
- if (
2184
- !isSameExpirationTime(
2185
- remainingExpirationTime,
2186
- (NoWork: ExpirationTimeOpaque),
2187
- )
2188
- ) {
1959
+ if (remainingLanes !== NoLanes) {
1960
if (enableSchedulerTracing) {
1961
if (spawnedWorkDuringRender !== null) {
1962
const expirationTimes = spawnedWorkDuringRender;
1969
);
1970
}
1971
}
2201
- schedulePendingInteractions(root, remainingExpirationTime);
1972
+ schedulePendingInteractions(root, remainingLanes);
1973
}
1974
} else {
1975
// If there's no remaining work, we can clear the set of already failed
1983
// Otherwise, we'll wait until after the passive effects are flushed.
1984
// Wait to do this until after remaining work has been scheduled,
1985
// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
2215
- finishPendingInteractions(root, expirationTime);
1986
+ finishPendingInteractions(root, lanes);
1987
}
1988
}
1989
2219
- if (
2220
- isSameExpirationTime(remainingExpirationTime, (Sync: ExpirationTimeOpaque))
2221
- ) {
1990
+ if (remainingLanes === SyncLane) {
1991
// Count the number of times the root synchronously re-renders without
1992
// finishing. If there are too many, it indicates an infinite update loop.
1993
if (root === rootWithNestedUpdates) {
2135
}
2136
}
2137
2369
-function commitLayoutEffects(
2370
- root: FiberRoot,
2371
- committedExpirationTime: ExpirationTimeOpaque,
2372
-) {
2138
+function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2139
// TODO: Should probably move the bulk of this function to commitWork.
2140
while (nextEffect !== null) {
2141
setCurrentDebugFiberInDEV(nextEffect);
2144
2145
if (effectTag & (Update | Callback)) {
2146
const current = nextEffect.alternate;
2381
- commitLayoutEffectOnFiber(
2382
- root,
2383
- current,
2384
- nextEffect,
2385
- committedExpirationTime,
2386
- );
2147
+ commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
2148
}
2149
2150
if (effectTag & Ref) {
2232
}
2233
2234
const root = rootWithPendingPassiveEffects;
2474
- const expirationTime = pendingPassiveEffectsExpirationTime;
2235
+ const lanes = pendingPassiveEffectsLanes;
2236
rootWithPendingPassiveEffects = null;
2476
- pendingPassiveEffectsExpirationTime = NoWork;
2237
+ pendingPassiveEffectsLanes = NoLanes;
2238
2239
invariant(
2240
(executionContext & (RenderContext | CommitContext)) === NoContext,
2412
2413
if (enableSchedulerTracing) {
2414
popInteractions(((prevInteractions: any): Set<Interaction>));
2654
- finishPendingInteractions(root, expirationTime);
2415
+ finishPendingInteractions(root, lanes);
2416
}
2417
2418
if (__DEV__) {
2460
error: mixed,
2461
) {
2462
const errorInfo = createCapturedValue(error, sourceFiber);
2702
- const update = createRootErrorUpdate(
2703
- rootFiber,
2704
- errorInfo,
2705
- (Sync: ExpirationTimeOpaque),
2706
- );
2463
+ const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2464
enqueueUpdate(rootFiber, update);
2708
- const root = markUpdateTimeFromFiberToRoot(
2709
- rootFiber,
2710
- (Sync: ExpirationTimeOpaque),
2711
- );
2465
+ const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2466
if (root !== null) {
2467
ensureRootIsScheduled(root);
2714
- schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
2468
+ schedulePendingInteractions(root, SyncLane);
2469
}
2470
}
2471
2494
const update = createClassErrorUpdate(
2495
fiber,
2496
errorInfo,
2743
- (Sync: ExpirationTimeOpaque),
2497
+ (SyncLane: Lane),
2498
);
2499
enqueueUpdate(fiber, update);
2746
- const root = markUpdateTimeFromFiberToRoot(
2747
- fiber,
2748
- (Sync: ExpirationTimeOpaque),
2749
- );
2500
+ const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2501
if (root !== null) {
2502
ensureRootIsScheduled(root);
2752
- schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
2503
+ schedulePendingInteractions(root, SyncLane);
2504
}
2505
return;
2506
}
2512
export function pingSuspendedRoot(
2513
root: FiberRoot,
2514
wakeable: Wakeable,
2764
- suspendedTime: ExpirationTimeOpaque,
2515
+ pingedLanes: Lanes,
2516
) {
2517
const pingCache = root.pingCache;
2518
if (pingCache !== null) {
2521
pingCache.delete(wakeable);
2522
}
2523
2524
+ markRootPinged(root, pingedLanes);
2525
+
2526
if (
2527
workInProgressRoot === root &&
2775
- isSameExpirationTime(renderExpirationTime, suspendedTime)
2528
+ isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
2529
) {
2530
// Received a ping at the same priority level at which we're currently
2531
// rendering. We might want to restart this render. This should mirror
2546
workInProgressRootLatestProcessedEventTime === -1 &&
2547
now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2548
) {
2796
- // Restart from the root. Don't need to schedule a ping because
2797
- // we're already working on this tree.
2798
- prepareFreshStack(root, renderExpirationTime);
2549
+ // Restart from the root.
2550
+ prepareFreshStack(root, NoLanes);
2551
} else {
2552
// Even though we can't restart right now, we might get an
2553
// opportunity later. So we mark this render as having a ping.
2802
- workInProgressRootHasPendingPing = true;
2554
+ workInProgressRootPingedLanes = mergeLanes(
2555
+ workInProgressRootPingedLanes,
2556
+ pingedLanes,
2557
+ );
2558
}
2804
- return;
2805
- }
2806
-
2807
- if (!isRootSuspendedAtTime(root, suspendedTime)) {
2808
- // The root is no longer suspended at this time.
2809
- return;
2559
}
2560
2812
- const lastPingedTime = root.lastPingedTime_opaque;
2813
- if (
2814
- !isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) &&
2815
- !isSameOrHigherPriority(lastPingedTime, suspendedTime)
2816
- ) {
2817
- // There's already a lower priority ping scheduled.
2818
- return;
2819
- }
2820
-
2821
- // Mark the time at which this ping was scheduled.
2822
- root.lastPingedTime_opaque = suspendedTime;
2823
-
2561
ensureRootIsScheduled(root);
2825
- schedulePendingInteractions(root, suspendedTime);
2562
+ schedulePendingInteractions(root, pingedLanes);
2563
}
2564
2828
-function retryTimedOutBoundary(
2829
- boundaryFiber: Fiber,
2830
- retryTime: ExpirationTimeOpaque,
2831
-) {
2565
+function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
2566
// The boundary fiber (a Suspense component or SuspenseList component)
2567
// previously was rendered in its fallback state. One of the promises that
2568
// suspended it has resolved, which means at least part of the tree was
2569
// likely unblocked. Try rendering again, at a new expiration time.
2836
- if (isSameExpirationTime(retryTime, (NoWork: ExpirationTimeOpaque))) {
2570
+ if (retryLane === NoLane) {
2571
const suspenseConfig = null; // Retries don't carry over the already committed update.
2838
- retryTime = requestUpdateExpirationTime(boundaryFiber, suspenseConfig);
2572
+ // TODO: Should retries get their own lane? Maybe it could share with
2573
+ // transitions.
2574
+ retryLane = requestUpdateLane(boundaryFiber, suspenseConfig);
2575
}
2576
// TODO: Special case idle priority?
2841
- const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
2577
+ const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
2578
if (root !== null) {
2579
ensureRootIsScheduled(root);
2844
- schedulePendingInteractions(root, retryTime);
2580
+ schedulePendingInteractions(root, retryLane);
2581
}
2582
}
2583
2584
export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
2585
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2850
- let retryTime = NoWork;
2586
+ let retryLane = NoLane;
2587
if (suspenseState !== null) {
2852
- retryTime = suspenseState.retryTime;
2588
+ retryLane = suspenseState.retryLane;
2589
}
2854
- retryTimedOutBoundary(boundaryFiber, retryTime);
2590
+ retryTimedOutBoundary(boundaryFiber, retryLane);
2591
}
2592
2593
export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2858
- let retryTime = NoWork; // Default
2594
+ let retryLane = NoLane; // Default
2595
let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
2596
if (enableSuspenseServerRenderer) {
2597
switch (boundaryFiber.tag) {
2599
retryCache = boundaryFiber.stateNode;
2600
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2601
if (suspenseState !== null) {
2866
- retryTime = suspenseState.retryTime;
2602
+ retryLane = suspenseState.retryLane;
2603
}
2604
break;
2605
case SuspenseListComponent:
2622
retryCache.delete(wakeable);
2623
}
2624
2889
- retryTimedOutBoundary(boundaryFiber, retryTime);
2625
+ retryTimedOutBoundary(boundaryFiber, retryLane);
2626
}
2627
2628
// Computes the next Just Noticeable Difference (JND) boundary.
2790
let beginWork;
2791
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2792
const dummyFiber = null;
3057
- beginWork = (current, unitOfWork, expirationTime) => {
2793
+ beginWork = (current, unitOfWork, lanes) => {
2794
// If a component throws an error, we replay it again in a synchronously
2795
// dispatched event, so that the debugger will treat it as an uncaught
2796
// error See ReactErrorUtils for more information.
2802
unitOfWork,
2803
);
2804
try {
3069
- return originalBeginWork(current, unitOfWork, expirationTime);
2805
+ return originalBeginWork(current, unitOfWork, lanes);
2806
} catch (originalError) {
2807
if (
2808
originalError !== null &&
2838
null,
2839
current,
2840
unitOfWork,
3105
- expirationTime,
2841
+ lanes,
2842
);
2843
2844
if (hasCaughtError()) {
3052
}
3053
}
3054
3319
-function computeThreadID(
3320
- root: FiberRoot,
3321
- expirationTime: ExpirationTimeOpaque,
3322
-) {
3055
+function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
3056
// Interaction threads are unique per root and expiration time.
3057
// NOTE: Intentionally unsound cast. All that matters is that it's a number
3058
// and it represents a batch of work. Could make a helper function instead,
3059
// but meh this is fine for now.
3327
- return (expirationTime: any) * 1000 + root.interactionThreadID;
3060
+ return (lane: any) * 1000 + root.interactionThreadID;
3061
}
3062
3330
-export function markSpawnedWork(expirationTime: ExpirationTimeOpaque) {
3063
+export function markSpawnedWork(lane: Lane | Lanes) {
3064
if (!enableSchedulerTracing) {
3065
return;
3066
}
3067
if (spawnedWorkDuringRender === null) {
3335
- spawnedWorkDuringRender = [expirationTime];
3068
+ spawnedWorkDuringRender = [lane];
3069
} else {
3337
- spawnedWorkDuringRender.push(expirationTime);
3070
+ spawnedWorkDuringRender.push(lane);
3071
}
3072
}
3073
3074
function scheduleInteractions(
3075
root: FiberRoot,
3343
- expirationTime: ExpirationTimeOpaque,
3076
+ lane: Lane | Lanes,
3077
interactions: Set<Interaction>,
3078
) {
3079
if (!enableSchedulerTracing) {
3082
3083
if (interactions.size > 0) {
3084
const pendingInteractionMap = root.pendingInteractionMap_new;
3352
- const pendingInteractions = pendingInteractionMap.get(expirationTime);
3085
+ const pendingInteractions = pendingInteractionMap.get(lane);
3086
if (pendingInteractions != null) {
3087
interactions.forEach(interaction => {
3088
if (!pendingInteractions.has(interaction)) {
3093
pendingInteractions.add(interaction);
3094
});
3095
} else {
3363
- pendingInteractionMap.set(expirationTime, new Set(interactions));
3096
+ pendingInteractionMap.set(lane, new Set(interactions));
3097
3098
// Update the pending async work count for the current interactions.
3099
interactions.forEach(interaction => {
3103
3104
const subscriber = __subscriberRef.current;
3105
if (subscriber !== null) {
3373
- const threadID = computeThreadID(root, expirationTime);
3106
+ const threadID = computeThreadID(root, lane);
3107
subscriber.onWorkScheduled(interactions, threadID);
3108
}
3109
}
3110
}
3111
3379
-function schedulePendingInteractions(
3380
- root: FiberRoot,
3381
- expirationTime: ExpirationTimeOpaque,
3382
-) {
3112
+function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
3113
// This is called when work is scheduled on a root.
3114
// It associates the current interactions with the newly-scheduled expiration.
3115
// They will be restored when that expiration is later committed.
3117
return;
3118
}
3119
3390
- scheduleInteractions(root, expirationTime, __interactionsRef.current);
3120
+ scheduleInteractions(root, lane, __interactionsRef.current);
3121
}
3122
3393
-function startWorkOnPendingInteractions(
3394
- root: FiberRoot,
3395
- expirationTime: ExpirationTimeOpaque,
3396
-) {
3123
+function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
3124
// This is called when new work is started on a root.
3125
if (!enableSchedulerTracing) {
3126
return;
3131
// work triggered during the render phase will be associated with it.
3132
const interactions: Set<Interaction> = new Set();
3133
root.pendingInteractionMap_new.forEach(
3407
- (scheduledInteractions, scheduledExpirationTime) => {
3408
- if (isSameOrHigherPriority(scheduledExpirationTime, expirationTime)) {
3134
+ (scheduledInteractions, scheduledLane) => {
3135
+ if (includesSomeLane(lanes, scheduledLane)) {
3136
scheduledInteractions.forEach(interaction =>
3137
interactions.add(interaction),
3138
);
3150
if (interactions.size > 0) {
3151
const subscriber = __subscriberRef.current;
3152
if (subscriber !== null) {
3426
- const threadID = computeThreadID(root, expirationTime);
3153
+ const threadID = computeThreadID(root, lanes);
3154
try {
3155
subscriber.onWorkStarted(interactions, threadID);
3156
} catch (error) {
3163
}
3164
}
3165
3439
-function finishPendingInteractions(root, committedExpirationTime) {
3166
+function finishPendingInteractions(root, committedLanes) {
3167
if (!enableSchedulerTracing) {
3168
return;
3169
}
3170
3444
- const earliestRemainingTimeAfterCommit = root.firstPendingTime_opaque;
3171
+ const remainingLanesAfterCommit = root.pendingLanes;
3172
3173
let subscriber;
3174
3175
try {
3176
subscriber = __subscriberRef.current;
3177
if (subscriber !== null && root.memoizedInteractions.size > 0) {
3451
- const threadID = computeThreadID(root, committedExpirationTime);
3178
+ // FIXME: More than one lane can finish in a single commit.
3179
+ const threadID = computeThreadID(root, committedLanes);
3180
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
3181
}
3182
} catch (error) {
3189
// Unless the render was suspended or cascading work was scheduled,
3190
// In which case– leave pending interactions until the subsequent render.
3191
const pendingInteractionMap = root.pendingInteractionMap_new;
3464
- pendingInteractionMap.forEach(
3465
- (scheduledInteractions, scheduledExpirationTime) => {
3466
- // Only decrement the pending interaction count if we're done.
3467
- // If there's still work at the current priority,
3468
- // That indicates that we are waiting for suspense data.
3469
- if (
3470
- !isSameOrHigherPriority(
3471
- earliestRemainingTimeAfterCommit,
3472
- scheduledExpirationTime,
3473
- )
3474
- ) {
3475
- pendingInteractionMap.delete(scheduledExpirationTime);
3192
+ pendingInteractionMap.forEach((scheduledInteractions, lane) => {
3193
+ // Only decrement the pending interaction count if we're done.
3194
+ // If there's still work at the current priority,
3195
+ // That indicates that we are waiting for suspense data.
3196
+ if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
3197
+ pendingInteractionMap.delete(lane);
3198
3477
- scheduledInteractions.forEach(interaction => {
3478
- interaction.__count--;
3199
+ scheduledInteractions.forEach(interaction => {
3200
+ interaction.__count--;
3201
3480
- if (subscriber !== null && interaction.__count === 0) {
3481
- try {
3482
- subscriber.onInteractionScheduledWorkCompleted(interaction);
3483
- } catch (error) {
3484
- // If the subscriber throws, rethrow it in a separate task
3485
- scheduleCallback(ImmediateSchedulerPriority, () => {
3486
- throw error;
3487
- });
3488
- }
3202
+ if (subscriber !== null && interaction.__count === 0) {
3203
+ try {
3204
+ subscriber.onInteractionScheduledWorkCompleted(interaction);
3205
+ } catch (error) {
3206
+ // If the subscriber throws, rethrow it in a separate task
3207
+ scheduleCallback(ImmediateSchedulerPriority, () => {
3208
+ throw error;
3209
+ });
3210
}
3490
- });
3491
- }
3492
- },
3493
- );
3211
+ }
3212
+ });
3213
+ }
3214
+ });
3215
}
3216
}
3217