Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fiber] Refactor Pending Work Phase and Progressed Work #7636

Merged
merged 8 commits into from
Sep 13, 2016
16 changes: 6 additions & 10 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,12 @@ var ReactNoop = {
' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
'[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
);
const childInProgress = fiber.childInProgress;
if (childInProgress) {
if (childInProgress === fiber.child) {
console.log(' '.repeat(depth + 1) + 'ERROR: IN PROGRESS == CURRENT');
} else {
console.log(' '.repeat(depth + 1) + 'IN PROGRESS');
logFiber(childInProgress, depth + 1);
if (fiber.child) {
console.log(' '.repeat(depth + 1) + 'CURRENT');
}
const childInProgress = fiber.progressedChild;
if (childInProgress && childInProgress !== fiber.child) {
console.log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
logFiber(childInProgress, depth + 1);
if (fiber.child) {
console.log(' '.repeat(depth + 1) + 'CURRENT');
}
}
if (fiber.child) {
Expand Down
50 changes: 48 additions & 2 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ function ChildReconciler(shouldClone) {
// Will fix reconciliation properly later.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingWorkPriority = priority;
}
clone.pendingProps = element.props;
clone.child = existingChild.child;
clone.sibling = null;
clone.return = returnFiber;
previousSibling.sibling = clone;
Expand Down Expand Up @@ -134,10 +134,10 @@ function ChildReconciler(shouldClone) {
// Get the clone of the existing fiber.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingWorkPriority = priority;
}
clone.pendingProps = element.props;
clone.child = existingChild.child;
clone.sibling = null;
clone.return = returnFiber;
return clone;
Expand Down Expand Up @@ -219,3 +219,49 @@ function ChildReconciler(shouldClone) {
exports.reconcileChildFibers = ChildReconciler(true);

exports.reconcileChildFibersInPlace = ChildReconciler(false);


function cloneSiblings(current : Fiber, workInProgress : Fiber, returnFiber : Fiber) {
workInProgress.return = returnFiber;
while (current.sibling) {
current = current.sibling;
workInProgress = workInProgress.sibling = cloneFiber(
current,
current.pendingWorkPriority
);
workInProgress.return = returnFiber;
}
workInProgress.sibling = null;
}

exports.cloneChildFibers = function(current : ?Fiber, workInProgress : Fiber) {
if (!workInProgress.child) {
return;
}
if (current && workInProgress.child === current.child) {
// We use workInProgress.child since that lets Flow know that it can't be
// null since we validated that already. However, as the line above suggests
// they're actually the same thing.
const currentChild = workInProgress.child;
// TODO: This used to reset the pending priority. Not sure if that is needed.
// workInProgress.pendingWorkPriority = current.pendingWorkPriority;
// TODO: The below priority used to be set to NoWork which would've
// dropped work. This is currently unobservable but will become
// observable when the first sibling has lower priority work remaining
// than the next sibling. At that point we should add tests that catches
// this.
const newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
workInProgress.child = newChild;
cloneSiblings(currentChild, newChild, workInProgress);
}

// If there is no alternate, then we don't need to clone the children.
// If the children of the alternate fiber is a different set, then we don't
// need to clone. We need to reset the return fiber though since we'll
// traverse down into them.
let child = workInProgress.child;
while (child) {
child.return = workInProgress;
child = child.sibling;
}
};
66 changes: 51 additions & 15 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,44 @@ export type Fiber = Instance & {
firstEffect: ?Fiber,
lastEffect: ?Fiber,


// This will be used to quickly determine if a subtree has no pending changes.
pendingWorkPriority: PriorityLevel,

// This value represents the priority level that was last used to process this
// component. This indicates whether it is better to continue from the
// progressed work or if it is better to continue from the current state.
progressedPriority: PriorityLevel,

// If work bails out on a Fiber that already had some work started at a lower
// priority, then we need to store the progressed work somewhere. This holds
// the started child set until we need to get back to working on it. It may
// or may not be the same as the "current" child.
progressedChild: ?Fiber,

// This is a pooled version of a Fiber. Every fiber that gets updated will
// eventually have a pair. There are cases when we can clean up pairs to save
// memory if we need to.
alternate: ?Fiber,

// Keeps track of the children that are currently being processed but have not
// yet completed.
childInProgress: ?Fiber,

// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.

};

// This is a constructor of a POJO instead of a constructor function for a few
// reasons:
// 1) Nobody should add any instance methods on this. Instance methods can be
// more difficult to predict when they get optimized and they are almost
// never inlined properly in static compilers.
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
// always know when it is a fiber.
// 3) We can easily go from a createFiber call to calling a constructor if that
// is faster. The opposite is not true.
// 4) We might want to experiment with using numeric keys since they are easier
// to optimize in a non-JIT environment.
// 5) It should be easy to port this to a C struct and keep a C implementation
// compatible.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like the time the React docs mentioned in passing an experiment using React to build a native app, and then a few years later...

var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
return {

Expand Down Expand Up @@ -139,8 +158,8 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
lastEffect: null,

pendingWorkPriority: NoWork,

childInProgress: null,
progressedPriority: NoWork,
progressedChild: null,

alternate: null,

Expand All @@ -152,7 +171,16 @@ function shouldConstruct(Component) {
}

// This is used to create an alternate fiber to do work on.
// TODO: Rename to createWorkInProgressFiber or something like that.
exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fiber {
// We clone to get a work in progress. That means that this fiber is the
// current. To make it safe to reuse that fiber later on as work in progress
// we need to reset its work in progress flag now. We don't have an
// opportunity to do this earlier since we don't traverse the tree when
// the work in progress tree becomes the current tree.
// fiber.progressedPriority = NoWork;
// fiber.progressedChild = null;

// We use a double buffering pooling technique because we know that we'll only
// ever need at most two versions of a tree. We pool the "other" unused node
// that we're free to reuse. This is lazily created to avoid allocating extra
Expand All @@ -161,13 +189,15 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
let alt = fiber.alternate;
if (alt) {
alt.stateNode = fiber.stateNode;
alt.child = fiber.child;
alt.childInProgress = fiber.childInProgress;
alt.sibling = fiber.sibling;
alt.ref = alt.ref;
alt.pendingProps = fiber.pendingProps;
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
alt.ref = fiber.ref;
alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
alt.pendingWorkPriority = priorityLevel;

alt.child = fiber.child;
alt.memoizedProps = fiber.memoizedProps;
alt.output = fiber.output;

// Whenever we clone, we do so to get a new work in progress.
// This ensures that we've reset these in the new tree.
alt.nextEffect = null;
Expand All @@ -182,13 +212,19 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
alt.type = fiber.type;
alt.stateNode = fiber.stateNode;
alt.child = fiber.child;
alt.childInProgress = fiber.childInProgress;
alt.sibling = fiber.sibling;
alt.ref = alt.ref;
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
alt.ref = fiber.ref;
// pendingProps is here for symmetry but is unnecessary in practice for now.
// TODO: Pass in the new pendingProps as an argument maybe?
alt.pendingProps = fiber.pendingProps;
alt.pendingWorkPriority = priorityLevel;

alt.memoizedProps = fiber.memoizedProps;
alt.output = fiber.output;

alt.progressedChild = fiber.progressedChild;
alt.progressedPriority = fiber.progressedPriority;

alt.alternate = fiber;
fiber.alternate = alt;
return alt;
Expand Down