Skip to content

Commit 75e2a6e

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 3965001 commit 75e2a6e

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

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

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

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

0 commit comments

Comments
 (0)