Skip to content

Commit

Permalink
Initial version of blocking & top-level resuming
Browse files Browse the repository at this point in the history
Refactors the complete phase to add support for blocking a tree from
committing. Also adds a basic version of "resuming" for HostRoots.

Neither of these features are actually implemented in this commit; will
come later.
  • Loading branch information
acdlite committed Sep 29, 2017
1 parent 03c3618 commit 24b7272
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,16 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
function updateHostRoot(current, workInProgress, renderExpirationTime) {
const root = (workInProgress.stateNode: FiberRoot);
pushHostRootContext(workInProgress);
if (root.completedAt === renderExpirationTime) {
// The root is already complete. Bail out and commit.
// TODO: This is a limited version of resuming that only applies to
// the root, to account for the pathological case where a completed
// root must be completely restarted before it can commit. Once we
// implement resuming for real, this special branch shouldn't
// be neccessary.
return null;
}

const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
const prevState = workInProgress.memoizedState;
Expand Down
15 changes: 15 additions & 0 deletions src/renderers/shared/fiber/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
'use strict';

import type {Fiber} from 'ReactFiber';
import type {ExpirationTime} from 'ReactFiberExpirationTime';

const {createHostRootFiber} = require('ReactFiber');
const {Done} = require('ReactFiberExpirationTime');

export type FiberRoot = {
// Any additional information from the host associated with this root.
Expand All @@ -21,13 +23,25 @@ export type FiberRoot = {
current: Fiber,
// Determines if this root has already been added to the schedule for work.
isScheduled: boolean,
// The time at which this root completed.
completedAt: ExpirationTime,
// The work schedule is a linked list.
nextScheduledRoot: FiberRoot | null,
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
};

// Indicates whether the root is blocked from committing at a particular
// expiration time.
exports.isRootBlocked = function(
root: FiberRoot,
time: ExpirationTime,
): boolean {
// TODO: Implementation
return false;
};

exports.createFiberRoot = function(containerInfo: any): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
Expand All @@ -36,6 +50,7 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
current: uninitializedFiber,
containerInfo: containerInfo,
isScheduled: false,
completedAt: Done,
nextScheduledRoot: null,
context: null,
pendingContext: null,
Expand Down
75 changes: 66 additions & 9 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ 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 @@ -260,10 +261,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}

function resetNextUnitOfWork() {
// Clear out roots with no more work on them, or if they have uncaught errors
// Clear out roots with no more work on them
while (
nextScheduledRoot !== null &&
nextScheduledRoot.current.expirationTime === Done
nextScheduledRoot.current.expirationTime === Done &&
nextScheduledRoot.completedAt === Done
) {
// Unschedule this root.
nextScheduledRoot.isScheduled = false;
Expand All @@ -287,10 +289,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
let earliestExpirationRoot = null;
let earliestExpirationTime = Done;
while (root !== null) {
let rootExpirationTime = shouldWorkOnRoot(root);
if (
root.current.expirationTime !== Done &&
rootExpirationTime !== Done &&
(earliestExpirationTime === Done ||
earliestExpirationTime > root.current.expirationTime)
earliestExpirationTime > rootExpirationTime)
) {
earliestExpirationTime = root.current.expirationTime;
earliestExpirationRoot = root;
Expand All @@ -307,10 +310,26 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// unfortunately this is it.
resetContextStack();

nextUnitOfWork = createWorkInProgress(
earliestExpirationRoot.current,
earliestExpirationTime,
);
if (earliestExpirationRoot.completedAt === nextRenderExpirationTime) {
// If the root is already complete, reuse the existing work-in-progress.
// TODO: This is a limited version of resuming that only applies to
// the root, to account for the pathological case where a completed
// root must be completely restarted before it can commit. Once we
// implement resuming for real, this special branch shouldn't
// be neccessary.
nextUnitOfWork = earliestExpirationRoot.current.alternate;
invariant(
nextUnitOfWork !== null,
'Expected a completed root to have a work-in-progress. This error ' +
'is likely caused by a bug in React. Please file an issue.',
);
} else {
earliestExpirationRoot.completedAt = Done;
nextUnitOfWork = createWorkInProgress(
earliestExpirationRoot.current,
earliestExpirationTime,
);
}
if (earliestExpirationRoot !== nextRenderedTree) {
// We've switched trees. Reset the nested update counter.
nestedUpdateCount = 0;
Expand All @@ -325,6 +344,37 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return;
}

// Indicates whether the root should be worked on. Not the same as whether a
// root has work, because work could be blocked.
function shouldWorkOnRoot(root: FiberRoot): ExpirationTime {
const completedAt = root.completedAt;
const expirationTime = root.current.expirationTime;

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

if (completedAt !== Done) {
// The root completed but was blocked from committing.

if (expirationTime < completedAt) {
// We have work that expires earlier than the completed root. Regardless
// of whether the root is blocked, we should work on it.
return expirationTime;
}

// There have been no higher priority updates since we completed the root.
// If it's still blocked, return Done, 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.
const isBlocked = isRootBlocked(root, expirationTime);
return isBlocked ? Done : completedAt;
}

return expirationTime;
}

function commitAllHostEffects() {
while (nextEffect !== null) {
if (__DEV__) {
Expand Down Expand Up @@ -450,6 +500,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
'in React. Please file an issue.',
);

root.completedAt = Done;

if (nextRenderExpirationTime <= mostRecentCurrentTime) {
// Keep track of the number of iterations to prevent an infinite
// update loop.
Expand Down Expand Up @@ -705,7 +757,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// We've reached the root. Mark the root as pending commit. Depending
// on how much time we have left, we'll either commit it now or in
// the next frame.
pendingCommit = workInProgress;
const root = workInProgress.stateNode;
if (isRootBlocked(root, nextRenderExpirationTime)) {
root.completedAt = workInProgress.expirationTime = nextRenderExpirationTime;
} else {
pendingCommit = workInProgress;
}
return null;
}
}
Expand Down

0 comments on commit 24b7272

Please sign in to comment.