Skip to content

Commit 081111b

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 d4a3187 commit 081111b

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 {Done, 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 !== Done && 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
@@ -321,8 +321,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
321321

322322
if (isPrerender) {
323323
// Block the root from committing at this expiration time.
324-
if (root.blockers === null) {
325-
root.blockers = createUpdateQueue();
324+
if (root.topLevelBlockers === null) {
325+
root.topLevelBlockers = createUpdateQueue();
326326
}
327327
const block = {
328328
priorityLevel: null,
@@ -334,7 +334,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
334334
isTopLevelUnmount: false,
335335
next: null,
336336
};
337-
insertUpdateIntoQueue(root.blockers, block, currentTime);
337+
insertUpdateIntoQueue(root.topLevelBlockers, block, currentTime);
338338
}
339339

340340
scheduleWork(current, expirationTime);
@@ -348,11 +348,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
348348
WorkNode.prototype.commit = function() {
349349
const root = this._reactRootContainer;
350350
const expirationTime = this._expirationTime;
351-
const blockers = root.blockers;
352-
if (blockers === null) {
351+
const topLevelBlockers = root.topLevelBlockers;
352+
if (topLevelBlockers === null) {
353353
return;
354354
}
355-
processUpdateQueue(blockers, null, null, null, expirationTime);
355+
processUpdateQueue(topLevelBlockers, null, null, null, expirationTime);
356356
expireWork(root, expirationTime);
357357
};
358358
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
@@ -26,11 +26,14 @@ export type FiberRoot = {
2626
current: Fiber,
2727
// Determines if this root has already been added to the schedule for work.
2828
isScheduled: boolean,
29+
// A queue that represents times at which the root is blocked by a
30+
// top-level update.
31+
topLevelBlockers: UpdateQueue<null> | null,
2932
// The time at which this root completed.
3033
completedAt: ExpirationTime,
31-
// A queue that represents times at which this root is blocked
34+
// If this root completed, isBlocked indicates whether it's blocked
3235
// from committing.
33-
blockers: UpdateQueue<null> | null,
36+
isBlocked: boolean,
3437
// A queue of callbacks that fire once their corresponding expiration time
3538
// has completed. Only fired once.
3639
completionCallbacks: UpdateQueue<null> | null,
@@ -46,16 +49,12 @@ export type FiberRoot = {
4649
hydrate: boolean,
4750
};
4851

49-
exports.isRootBlocked = function(
50-
root: FiberRoot,
51-
expirationTime: ExpirationTime,
52-
) {
53-
const blockers = root.blockers;
54-
if (blockers === null) {
55-
return false;
52+
exports.topLevelBlockedAt = function(root: FiberRoot) {
53+
const topLevelBlockers = root.topLevelBlockers;
54+
if (topLevelBlockers === null) {
55+
return Done;
5656
}
57-
const blockedAt = getUpdateQueueExpirationTime(blockers);
58-
return blockedAt !== Done && blockedAt <= expirationTime;
57+
return getUpdateQueueExpirationTime(topLevelBlockers);
5958
};
6059

6160
exports.createFiberRoot = function(containerInfo: any): FiberRoot {
@@ -67,8 +66,9 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
6766
containerInfo: containerInfo,
6867
isScheduled: false,
6968
completedAt: Done,
70-
blockers: null,
69+
isBlocked: false,
7170
completionCallbacks: null,
71+
topLevelBlockers: null,
7272
forceExpire: null,
7373
nextScheduledRoot: null,
7474
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 = Done;
339337
nextUnitOfWork = createWorkInProgress(
340338
earliestExpirationRoot.current,
341339
earliestExpirationTime,
342340
);
343341
}
342+
343+
earliestExpirationRoot.completedAt = Done;
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 === Done) {
367367
// There's no work in this tree.
368368
return Done;
369369
}
370-
371-
if (completedAt !== Done) {
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 !== Done &&
387-
workInProgress.expirationTime <= expirationTime)
388-
) {
389-
// We have more work. Restart the completed tree.
390-
root.completedAt = Done;
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 Done, 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 Done;
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 Done;
408376
}
409-
410377
return expirationTime;
411378
}
412379

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

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

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

0 commit comments

Comments
 (0)