Skip to content

Commit 9297052

Browse files
committed
Simplify top-level blockers
After thinking about how to implement blockers in general, I figured out how to simplify top-level blockers, too.
1 parent 9a2ac61 commit 9297052

File tree

4 files changed

+64
-102
lines changed

4 files changed

+64
-102
lines changed

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {HydrationContext} from 'ReactFiberHydrationContext';
1818
import type {FiberRoot} from 'ReactFiberRoot';
1919
import type {HostConfig} from 'ReactFiberReconciler';
2020

21+
var {topLevelBlockedAt} = require('ReactFiberRoot');
2122
var {reconcileChildFibers} = require('ReactChildFiber');
2223
var {
2324
popContextProvider,
@@ -40,7 +41,7 @@ var {
4041
Fragment,
4142
} = ReactTypeOfWork;
4243
var {Placement, Ref, Update} = ReactTypeOfSideEffect;
43-
var {Never} = ReactFiberExpirationTime;
44+
var {NoWork, Never} = ReactFiberExpirationTime;
4445

4546
var invariant = require('fbjs/lib/invariant');
4647

@@ -219,6 +220,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
219220
// TODO: Delete this when we delete isMounted and findDOMNode.
220221
workInProgress.effectTag &= ~Placement;
221222
}
223+
224+
// Check if the root is blocked by a top-level update.
225+
const blockedAt = topLevelBlockedAt(fiberRoot);
226+
fiberRoot.isBlocked =
227+
blockedAt !== NoWork && blockedAt <= renderExpirationTime;
222228
return null;
223229
}
224230
case HostComponent: {

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
355355

356356
if (isPrerender) {
357357
// Block the root from committing at this expiration time.
358-
if (root.blockers === null) {
359-
root.blockers = createUpdateQueue();
358+
if (root.topLevelBlockers === null) {
359+
root.topLevelBlockers = createUpdateQueue();
360360
}
361361
const block = {
362362
priorityLevel: null,
@@ -368,7 +368,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
368368
isTopLevelUnmount: false,
369369
next: null,
370370
};
371-
insertUpdateIntoQueue(root.blockers, block, currentTime);
371+
insertUpdateIntoQueue(root.topLevelBlockers, block, currentTime);
372372
}
373373

374374
scheduleWork(current, expirationTime);
@@ -382,11 +382,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
382382
WorkNode.prototype.commit = function() {
383383
const root = this._reactRootContainer;
384384
const expirationTime = this._expirationTime;
385-
const blockers = root.blockers;
386-
if (blockers === null) {
385+
const topLevelBlockers = root.topLevelBlockers;
386+
if (topLevelBlockers === null) {
387387
return;
388388
}
389-
processUpdateQueue(blockers, null, null, null, expirationTime);
389+
processUpdateQueue(topLevelBlockers, null, null, null, expirationTime);
390390
expireWork(root, expirationTime);
391391
};
392392
WorkNode.prototype.then = function(callback) {

src/renderers/shared/fiber/ReactFiberRoot.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ export type FiberRoot = {
2525
current: Fiber,
2626
// Determines if this root has already been added to the schedule for work.
2727
isScheduled: boolean,
28+
// A queue that represents times at which the root is blocked by a
29+
// top-level update.
30+
topLevelBlockers: UpdateQueue<null> | null,
2831
// The time at which this root completed.
2932
completedAt: ExpirationTime,
30-
// A queue that represents times at which this root is blocked
33+
// If this root completed, isBlocked indicates whether it's blocked
3134
// from committing.
32-
blockers: UpdateQueue<null> | null,
35+
isBlocked: boolean,
3336
// A queue of callbacks that fire once their corresponding expiration time
3437
// has completed. Only fired once.
3538
completionCallbacks: UpdateQueue<null> | null,
@@ -45,16 +48,12 @@ export type FiberRoot = {
4548
hydrate: boolean,
4649
};
4750

48-
exports.isRootBlocked = function(
49-
root: FiberRoot,
50-
expirationTime: ExpirationTime,
51-
) {
52-
const blockers = root.blockers;
53-
if (blockers === null) {
54-
return false;
51+
exports.topLevelBlockedAt = function(root: FiberRoot) {
52+
const topLevelBlockers = root.topLevelBlockers;
53+
if (topLevelBlockers === null) {
54+
return NoWork;
5555
}
56-
const blockedAt = getUpdateQueueExpirationTime(blockers);
57-
return blockedAt !== NoWork && blockedAt <= expirationTime;
56+
return getUpdateQueueExpirationTime(topLevelBlockers);
5857
};
5958

6059
exports.createFiberRoot = function(containerInfo: any): FiberRoot {
@@ -66,8 +65,9 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
6665
containerInfo: containerInfo,
6766
isScheduled: false,
6867
completedAt: NoWork,
69-
blockers: null,
68+
isBlocked: false,
7069
completionCallbacks: null,
70+
topLevelBlockers: null,
7171
forceExpire: null,
7272
nextScheduledRoot: null,
7373
context: null,

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 39 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ var {ReactCurrentOwner} = require('ReactGlobalSharedState');
5252
var getComponentName = require('getComponentName');
5353

5454
var {createWorkInProgress} = require('ReactFiber');
55-
var {isRootBlocked} = require('ReactFiberRoot');
5655
var {onCommitRoot} = require('ReactFiberDevToolsHook');
5756

5857
var {
@@ -335,12 +334,15 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
335334
'is likely caused by a bug in React. Please file an issue.',
336335
);
337336
} else {
338-
earliestExpirationRoot.completedAt = NoWork;
339337
nextUnitOfWork = createWorkInProgress(
340338
earliestExpirationRoot.current,
341339
earliestExpirationTime,
342340
);
343341
}
342+
343+
earliestExpirationRoot.completedAt = NoWork;
344+
earliestExpirationRoot.isBlocked = false;
345+
344346
if (earliestExpirationRoot !== nextRenderedTree) {
345347
// We've switched trees. Reset the nested update counter.
346348
nestedUpdateCount = 0;
@@ -360,53 +362,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
360362
// TODO: Find a better name for this function. It also schedules completion
361363
// callbacks, if a root is blocked.
362364
function shouldWorkOnRoot(root: FiberRoot): ExpirationTime {
363-
const completedAt = root.completedAt;
364365
const expirationTime = root.current.expirationTime;
365-
366366
if (expirationTime === NoWork) {
367367
// There's no work in this tree.
368368
return NoWork;
369369
}
370-
371-
if (completedAt !== NoWork) {
372-
// The root completed but was blocked from committing.
373-
if (expirationTime < completedAt) {
374-
// We have work that expires earlier than the completed root.
375-
return expirationTime;
376-
}
377-
378-
// If the expiration time of the pending work is equal to the time at
379-
// which we completed the work-in-progress, it's possible additional
380-
// work was scheduled that happens to fall within the same expiration
381-
// bucket. We need to check the work-in-progress fiber.
382-
if (expirationTime === completedAt) {
383-
const workInProgress = root.current.alternate;
384-
if (
385-
workInProgress !== null &&
386-
(workInProgress.expirationTime !== NoWork &&
387-
workInProgress.expirationTime <= expirationTime)
388-
) {
389-
// We have more work. Restart the completed tree.
390-
root.completedAt = NoWork;
391-
return expirationTime;
392-
}
393-
}
394-
395-
// There have been no higher priority updates since we completed the root.
396-
// If it's still blocked, return NoWork, as if it has no more work. If it's
397-
// no longer blocked, return the time at which it completed so that we
398-
// can commit it.
399-
if (isRootBlocked(root, completedAt)) {
400-
// We usually process completion callbacks right after a root is
401-
// completed. But this root already completed, and it's possible that
402-
// we received new completion callbacks since then.
403-
processCompletionCallbacks(root, completedAt);
404-
return NoWork;
405-
}
406-
407-
return completedAt;
370+
if (root.isBlocked) {
371+
// We usually process completion callbacks right after a root is
372+
// completed. But this root already completed, and it's possible that
373+
// we received new completion callbacks since then.
374+
processCompletionCallbacks(root, root.completedAt);
375+
return NoWork;
408376
}
409-
410377
return expirationTime;
411378
}
412379

@@ -817,16 +784,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
817784
workInProgress = returnFiber;
818785
continue;
819786
} else {
787+
// We've reached the root. Mark it as complete.
820788
const root = workInProgress.stateNode;
821-
// We've reached the root. Mark the root as complete. Depending on how
822-
// much time we have left, we'll either commit it now or in the
823-
// next frame.
824-
if (isRootBlocked(root, nextRenderExpirationTime)) {
825-
// The root is blocked from committing. Mark it as complete so we
826-
// know we can commit it later without starting new work.
827-
root.completedAt = nextRenderExpirationTime;
828-
} else {
829-
// The root is not blocked, so we can commit it now.
789+
root.completedAt = nextRenderExpirationTime;
790+
// If the root isn't blocked, it's ready to commit. If it is blocked,
791+
// we'll come back to it later.
792+
if (!root.isBlocked) {
830793
pendingCommit = workInProgress;
831794
}
832795
processCompletionCallbacks(root, nextRenderExpirationTime);
@@ -1509,25 +1472,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
15091472
}
15101473
}
15111474

1512-
function scheduleRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1513-
if (expirationTime === NoWork) {
1514-
return;
1515-
}
1516-
1517-
if (!root.isScheduled) {
1518-
root.isScheduled = true;
1519-
if (lastScheduledRoot) {
1520-
// Schedule ourselves to the end.
1521-
lastScheduledRoot.nextScheduledRoot = root;
1522-
lastScheduledRoot = root;
1523-
} else {
1524-
// We're the only work scheduled.
1525-
nextScheduledRoot = root;
1526-
lastScheduledRoot = root;
1527-
}
1528-
}
1529-
}
1530-
15311475
function scheduleUpdate(
15321476
fiber: Fiber,
15331477
partialState: mixed,
@@ -1549,6 +1493,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
15491493
isReplace,
15501494
isForced,
15511495
nextCallback: null,
1496+
isTopLevelUnmount: false,
15521497
next: null,
15531498
};
15541499
insertUpdateIntoFiber(fiber, update, currentTime);
@@ -1594,35 +1539,45 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
15941539
}
15951540

15961541
let node = fiber;
1597-
let shouldContinue = true;
1598-
while (node !== null && shouldContinue) {
1599-
// Walk the parent path to the root and update each node's expiration
1600-
// time. Once we reach a node whose expiration matches (and whose
1601-
// alternate's expiration matches) we can exit safely knowing that the
1602-
// rest of the path is correct.
1603-
shouldContinue = false;
1542+
while (node !== null) {
16041543
if (
16051544
node.expirationTime === NoWork ||
16061545
node.expirationTime > expirationTime
16071546
) {
1608-
// Expiration time did not match. Update and keep going.
1609-
shouldContinue = true;
16101547
node.expirationTime = expirationTime;
16111548
}
16121549
if (node.alternate !== null) {
16131550
if (
16141551
node.alternate.expirationTime === NoWork ||
16151552
node.alternate.expirationTime > expirationTime
16161553
) {
1617-
// Expiration time did not match. Update and keep going.
1618-
shouldContinue = true;
16191554
node.alternate.expirationTime = expirationTime;
16201555
}
16211556
}
16221557
if (node.return === null) {
16231558
if (node.tag === HostRoot) {
16241559
const root: FiberRoot = (node.stateNode: any);
1625-
scheduleRoot(root, expirationTime);
1560+
1561+
// Add the root to the work schedule.
1562+
if (expirationTime !== NoWork) {
1563+
root.isBlocked = false;
1564+
if (!root.isScheduled) {
1565+
root.isScheduled = true;
1566+
if (lastScheduledRoot) {
1567+
// Schedule ourselves to the end.
1568+
lastScheduledRoot.nextScheduledRoot = root;
1569+
lastScheduledRoot = root;
1570+
} else {
1571+
// We're the only work scheduled.
1572+
nextScheduledRoot = root;
1573+
lastScheduledRoot = root;
1574+
}
1575+
}
1576+
}
1577+
1578+
// If we're not current performing work, we need to either start
1579+
// working now (if the update is synchronous) or schedule a callback
1580+
// to perform work later.
16261581
if (!isPerformingWork) {
16271582
const priorityLevel = expirationTimeToPriorityLevel(
16281583
mostRecentCurrentTime,
@@ -1774,6 +1729,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
17741729
'Cannot commit while already performing work.',
17751730
);
17761731
root.forceExpire = expirationTime;
1732+
root.isBlocked = false;
17771733
try {
17781734
performWork(TaskPriority, null);
17791735
} finally {

0 commit comments

Comments
 (0)