Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
74 changes: 70 additions & 4 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var {

var ReactFiber = require('ReactFiber');
var ReactReifiedYield = require('ReactReifiedYield');
var ReactPriorityLevel = require('ReactPriorityLevel');

const {
cloneFiber,
Expand All @@ -38,6 +39,10 @@ const {
createReifiedYield,
} = ReactReifiedYield;

const {
NoWork,
} = ReactPriorityLevel;

const isArray = Array.isArray;

function ChildReconciler(shouldClone) {
Expand All @@ -63,10 +68,15 @@ function ChildReconciler(shouldClone) {
// Will fix reconciliation properly later.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
clone.pendingWorkPriority = priority;
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingUpdatePriority = priority;
if (clone.pendingWorkPriority === NoWork ||
clone.pendingWorkPriority > priority) {
clone.pendingWorkPriority = priority;
}
}
clone.pendingProps = element.props;
clone.child = existingChild.child;
// clone.child = existingChild.child;
clone.sibling = null;
clone.return = returnFiber;
previousSibling.sibling = clone;
Expand Down Expand Up @@ -134,10 +144,15 @@ function ChildReconciler(shouldClone) {
// Get the clone of the existing fiber.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
clone.pendingWorkPriority = priority;
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingUpdatePriority = priority;
if (clone.pendingWorkPriority === NoWork ||
clone.pendingWorkPriority > priority) {
clone.pendingWorkPriority = priority;
}
}
clone.pendingProps = element.props;
clone.child = existingChild.child;
// clone.child = existingChild.child;
clone.sibling = null;
clone.return = returnFiber;
return clone;
Expand Down Expand Up @@ -219,3 +234,54 @@ 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(workInProgress : Fiber) {
if (!workInProgress.child) {
return;
}
const current = workInProgress.alternate;
if (!current || workInProgress.child !== current.child) {
// 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;
}
return;
}
// 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 currentChild = workInProgress.child;
if (!currentChild) {
return;
}
workInProgress.child = cloneFiber(
currentChild,
currentChild.pendingWorkPriority
);
cloneSiblings(currentChild, workInProgress.child, workInProgress);
};
114 changes: 98 additions & 16 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
import type { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';

var ReactTypeOfWork = require('ReactTypeOfWork');
var {
Expand Down Expand Up @@ -76,6 +77,12 @@ export type Fiber = Instance & {
pendingProps: any, // This type will be more specific once we overload the tag.
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
memoizedProps: any, // The props used to create the output.
// A queue of local state updates.
updateQueue: ?UpdateQueue,
// The state used to create the output. This is a full state object.
memoizedState: any,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
// Output is the return value of this fiber, or a linked list of return values
// if this returns multiple values. Such as a fragment.
output: any, // This type will be more specific once we overload the tag.
Expand All @@ -89,25 +96,49 @@ export type Fiber = Instance & {
firstEffect: ?Fiber,
lastEffect: ?Fiber,

// The update priority is the priority of a fiber's pending props and state.
// It may be lower than the priority of the entire subtree.
pendingUpdatePriority: PriorityLevel,

// This will be used to quickly determine if a subtree has no pending changes.
// The work priority is the priority of the entire subtree. It 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.
var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
return {

Expand All @@ -132,15 +163,19 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {

pendingProps: null,
memoizedProps: null,
updateQueue: null,
memoizedState: null,
callbackList: null,
output: null,

nextEffect: null,
firstEffect: null,
lastEffect: null,

pendingUpdatePriority: NoWork,
pendingWorkPriority: NoWork,

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

alternate: null,

Expand All @@ -152,7 +187,38 @@ 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;

// Don't deprioritize when cloning. Unlike other priority comparisons (e.g.
// in the scheduler), this one must check that priorityLevel is not equal to
// NoWork, otherwise work will be dropped. For complete correctness, the other
// priority comparisons should also perform this check, even though it's not
// an issue in practice. I didn't catch this at first and it created a subtle
// bug, which suggests we may need to extract the logic into a
// utility function (shouldOverridePriority).
let updatePriority;
let workPriority;
if (priorityLevel !== NoWork &&
(priorityLevel < fiber.pendingUpdatePriority || fiber.pendingUpdatePriority === NoWork)) {
updatePriority = priorityLevel;
} else {
updatePriority = fiber.pendingUpdatePriority;
}
if (updatePriority !== NoWork &&
(updatePriority < fiber.pendingWorkPriority || fiber.pendingWorkPriority === NoWork)) {
workPriority = updatePriority;
} else {
workPriority = fiber.pendingWorkPriority;
}

// 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,12 +227,17 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
let alt = fiber.alternate;
if (alt) {
alt.stateNode = fiber.stateNode;
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
alt.ref = fiber.ref;
alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
alt.updateQueue = fiber.updateQueue;
alt.callbackList = fiber.callbackList;
alt.pendingUpdatePriority = updatePriority;
alt.pendingWorkPriority = workPriority;

alt.child = fiber.child;
alt.childInProgress = fiber.childInProgress;
alt.sibling = fiber.sibling;
alt.ref = alt.ref;
alt.pendingProps = fiber.pendingProps;
alt.pendingWorkPriority = priorityLevel;
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.
Expand All @@ -182,12 +253,21 @@ 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.updateQueue = fiber.updateQueue;
alt.callbackList = fiber.callbackList;
alt.pendingUpdatePriority = updatePriority;
alt.pendingWorkPriority = workPriority;

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

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

alt.alternate = fiber;
fiber.alternate = alt;
Expand All @@ -203,6 +283,7 @@ exports.createFiberFromElement = function(element : ReactElement<*>, priorityLev
// $FlowFixMe: ReactElement.key is currently defined as ?string but should be defined as null | string in Flow.
const fiber = createFiberFromElementType(element.type, element.key);
fiber.pendingProps = element.props;
fiber.pendingUpdatePriority = priorityLevel;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
Expand Down Expand Up @@ -232,6 +313,7 @@ exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priority
const fiber = createFiber(CoroutineComponent, coroutine.key);
fiber.type = coroutine.handler;
fiber.pendingProps = coroutine;
fiber.pendingUpdatePriority = priorityLevel;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
Expand Down
Loading