Skip to content

Commit

Permalink
Simplify top-level blockers
Browse files Browse the repository at this point in the history
After thinking about how to implement blockers in general, I figured
out how to simplify top-level blockers, too.
  • Loading branch information
acdlite committed Oct 9, 2017
1 parent 5c5dc75 commit f10215d
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 102 deletions.
8 changes: 7 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {HydrationContext} from 'ReactFiberHydrationContext';
import type {FiberRoot} from 'ReactFiberRoot';
import type {HostConfig} from 'ReactFiberReconciler';

var {topLevelBlockedAt} = require('ReactFiberRoot');
var {reconcileChildFibers} = require('ReactChildFiber');
var {
popContextProvider,
Expand All @@ -40,7 +41,7 @@ var {
Fragment,
} = ReactTypeOfWork;
var {Placement, Ref, Update} = ReactTypeOfSideEffect;
var {Never} = ReactFiberExpirationTime;
var {NoWork, Never} = ReactFiberExpirationTime;

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

Expand Down Expand Up @@ -219,6 +220,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag &= ~Placement;
}

// Check if the root is blocked by a top-level update.
const blockedAt = topLevelBlockedAt(fiberRoot);
fiberRoot.isBlocked =
blockedAt !== NoWork && blockedAt <= renderExpirationTime;
return null;
}
case HostComponent: {
Expand Down
12 changes: 6 additions & 6 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(

if (isPrerender) {
// Block the root from committing at this expiration time.
if (root.blockers === null) {
root.blockers = createUpdateQueue();
if (root.topLevelBlockers === null) {
root.topLevelBlockers = createUpdateQueue();
}
const block = {
priorityLevel: null,
Expand All @@ -368,7 +368,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
isTopLevelUnmount: false,
next: null,
};
insertUpdateIntoQueue(root.blockers, block, currentTime);
insertUpdateIntoQueue(root.topLevelBlockers, block, currentTime);
}

scheduleWork(current, expirationTime);
Expand All @@ -382,11 +382,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
WorkNode.prototype.commit = function() {
const root = this._reactRootContainer;
const expirationTime = this._expirationTime;
const blockers = root.blockers;
if (blockers === null) {
const topLevelBlockers = root.topLevelBlockers;
if (topLevelBlockers === null) {
return;
}
processUpdateQueue(blockers, null, null, null, expirationTime);
processUpdateQueue(topLevelBlockers, null, null, null, expirationTime);
expireWork(root, expirationTime);
};
WorkNode.prototype.then = function(callback) {
Expand Down
24 changes: 12 additions & 12 deletions src/renderers/shared/fiber/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ export type FiberRoot = {
current: Fiber,
// Determines if this root has already been added to the schedule for work.
isScheduled: boolean,
// A queue that represents times at which the root is blocked by a
// top-level update.
topLevelBlockers: UpdateQueue<null> | null,
// The time at which this root completed.
completedAt: ExpirationTime,
// A queue that represents times at which this root is blocked
// If this root completed, isBlocked indicates whether it's blocked
// from committing.
blockers: UpdateQueue<null> | null,
isBlocked: boolean,
// A queue of callbacks that fire once their corresponding expiration time
// has completed. Only fired once.
completionCallbacks: UpdateQueue<null> | null,
Expand All @@ -45,16 +48,12 @@ export type FiberRoot = {
hydrate: boolean,
};

exports.isRootBlocked = function(
root: FiberRoot,
expirationTime: ExpirationTime,
) {
const blockers = root.blockers;
if (blockers === null) {
return false;
exports.topLevelBlockedAt = function(root: FiberRoot) {
const topLevelBlockers = root.topLevelBlockers;
if (topLevelBlockers === null) {
return NoWork;
}
const blockedAt = getUpdateQueueExpirationTime(blockers);
return blockedAt !== NoWork && blockedAt <= expirationTime;
return getUpdateQueueExpirationTime(topLevelBlockers);
};

exports.createFiberRoot = function(containerInfo: any): FiberRoot {
Expand All @@ -66,8 +65,9 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
containerInfo: containerInfo,
isScheduled: false,
completedAt: NoWork,
blockers: null,
isBlocked: false,
completionCallbacks: null,
topLevelBlockers: null,
forceExpire: null,
nextScheduledRoot: null,
context: null,
Expand Down
122 changes: 39 additions & 83 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ var {ReactCurrentOwner} = require('ReactGlobalSharedState');
var getComponentName = require('getComponentName');

var {createWorkInProgress} = require('ReactFiber');
var {isRootBlocked} = require('ReactFiberRoot');
var {onCommitRoot} = require('ReactFiberDevToolsHook');

var {
Expand Down Expand Up @@ -335,12 +334,15 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
'is likely caused by a bug in React. Please file an issue.',
);
} else {
earliestExpirationRoot.completedAt = NoWork;
nextUnitOfWork = createWorkInProgress(
earliestExpirationRoot.current,
earliestExpirationTime,
);
}

earliestExpirationRoot.completedAt = NoWork;
earliestExpirationRoot.isBlocked = false;

if (earliestExpirationRoot !== nextRenderedTree) {
// We've switched trees. Reset the nested update counter.
nestedUpdateCount = 0;
Expand All @@ -360,53 +362,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// TODO: Find a better name for this function. It also schedules completion
// callbacks, if a root is blocked.
function shouldWorkOnRoot(root: FiberRoot): ExpirationTime {
const completedAt = root.completedAt;
const expirationTime = root.current.expirationTime;

if (expirationTime === NoWork) {
// There's no work in this tree.
return NoWork;
}

if (completedAt !== NoWork) {
// The root completed but was blocked from committing.
if (expirationTime < completedAt) {
// We have work that expires earlier than the completed root.
return expirationTime;
}

// If the expiration time of the pending work is equal to the time at
// which we completed the work-in-progress, it's possible additional
// work was scheduled that happens to fall within the same expiration
// bucket. We need to check the work-in-progress fiber.
if (expirationTime === completedAt) {
const workInProgress = root.current.alternate;
if (
workInProgress !== null &&
(workInProgress.expirationTime !== NoWork &&
workInProgress.expirationTime <= expirationTime)
) {
// We have more work. Restart the completed tree.
root.completedAt = NoWork;
return expirationTime;
}
}

// There have been no higher priority updates since we completed the root.
// If it's still blocked, return NoWork, as if it has no more work. If it's
// no longer blocked, return the time at which it completed so that we
// can commit it.
if (isRootBlocked(root, completedAt)) {
// We usually process completion callbacks right after a root is
// completed. But this root already completed, and it's possible that
// we received new completion callbacks since then.
processCompletionCallbacks(root, completedAt);
return NoWork;
}

return completedAt;
if (root.isBlocked) {
// We usually process completion callbacks right after a root is
// completed. But this root already completed, and it's possible that
// we received new completion callbacks since then.
processCompletionCallbacks(root, root.completedAt);
return NoWork;
}

return expirationTime;
}

Expand Down Expand Up @@ -817,16 +784,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
workInProgress = returnFiber;
continue;
} else {
// We've reached the root. Mark it as complete.
const root = workInProgress.stateNode;
// We've reached the root. Mark the root as complete. Depending on how
// much time we have left, we'll either commit it now or in the
// next frame.
if (isRootBlocked(root, nextRenderExpirationTime)) {
// The root is blocked from committing. Mark it as complete so we
// know we can commit it later without starting new work.
root.completedAt = nextRenderExpirationTime;
} else {
// The root is not blocked, so we can commit it now.
root.completedAt = nextRenderExpirationTime;
// If the root isn't blocked, it's ready to commit. If it is blocked,
// we'll come back to it later.
if (!root.isBlocked) {
pendingCommit = workInProgress;
}
processCompletionCallbacks(root, nextRenderExpirationTime);
Expand Down Expand Up @@ -1509,25 +1472,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}

function scheduleRoot(root: FiberRoot, expirationTime: ExpirationTime) {
if (expirationTime === NoWork) {
return;
}

if (!root.isScheduled) {
root.isScheduled = true;
if (lastScheduledRoot) {
// Schedule ourselves to the end.
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
} else {
// We're the only work scheduled.
nextScheduledRoot = root;
lastScheduledRoot = root;
}
}
}

function scheduleUpdate(
fiber: Fiber,
partialState: mixed,
Expand All @@ -1549,6 +1493,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
isReplace,
isForced,
nextCallback: null,
isTopLevelUnmount: false,
next: null,
};
insertUpdateIntoFiber(fiber, update, currentTime);
Expand Down Expand Up @@ -1594,35 +1539,45 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}

let node = fiber;
let shouldContinue = true;
while (node !== null && shouldContinue) {
// Walk the parent path to the root and update each node's expiration
// time. Once we reach a node whose expiration matches (and whose
// alternate's expiration matches) we can exit safely knowing that the
// rest of the path is correct.
shouldContinue = false;
while (node !== null) {
if (
node.expirationTime === NoWork ||
node.expirationTime > expirationTime
) {
// Expiration time did not match. Update and keep going.
shouldContinue = true;
node.expirationTime = expirationTime;
}
if (node.alternate !== null) {
if (
node.alternate.expirationTime === NoWork ||
node.alternate.expirationTime > expirationTime
) {
// Expiration time did not match. Update and keep going.
shouldContinue = true;
node.alternate.expirationTime = expirationTime;
}
}
if (node.return === null) {
if (node.tag === HostRoot) {
const root: FiberRoot = (node.stateNode: any);
scheduleRoot(root, expirationTime);

// Add the root to the work schedule.
if (expirationTime !== NoWork) {
root.isBlocked = false;
if (!root.isScheduled) {
root.isScheduled = true;
if (lastScheduledRoot) {
// Schedule ourselves to the end.
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
} else {
// We're the only work scheduled.
nextScheduledRoot = root;
lastScheduledRoot = root;
}
}
}

// If we're not current performing work, we need to either start
// working now (if the update is synchronous) or schedule a callback
// to perform work later.
if (!isPerformingWork) {
const priorityLevel = expirationTimeToPriorityLevel(
mostRecentCurrentTime,
Expand Down Expand Up @@ -1774,6 +1729,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
'Cannot commit while already performing work.',
);
root.forceExpire = expirationTime;
root.isBlocked = false;
try {
performWork(TaskPriority, null);
} finally {
Expand Down

0 comments on commit f10215d

Please sign in to comment.