Skip to content
Merged
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
21 changes: 4 additions & 17 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2032,27 +2032,13 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
* should call callbacks even if updates are aborted
* can deprioritize unfinished work and resume it later
* can deprioritize a tree from without dropping work
* can resume work in a subtree even when a parent bails out
* can resume work in a bailed subtree within one pass
* can resume mounting a class component
* reuses the same instance when resuming a class instance
* can reuse work done after being preempted
* can reuse work that began but did not complete, after being preempted
* can reuse work if shouldComponentUpdate is false, after being preempted
* memoizes work even if shouldComponentUpdate returns false
* can update in the middle of a tree using setState
* can queue multiple state updates
* can use updater form of setState
* can call setState inside update callback
* can replaceState
* can forceUpdate
* can call sCU while resuming a partly mounted component
* gets new props when setting state on a partly updated component
* calls componentWillMount twice if the initial render is aborted
* uses state set in componentWillMount even if initial render was aborted
* calls componentWill* twice if an update render is aborted
* does not call componentWillReceiveProps for state-only updates
* skips will/DidUpdate when bailing unless an update was already in progress
* performs batched updates at the end of the batch
* can nest batchedUpdates
* can handle if setState callback throws
Expand All @@ -2064,7 +2050,6 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
* maintains the correct context when providers bail out due to low priority
* maintains the correct context when unwinding due to an error in render
* should not recreate masked context unless inputs have changed
* should reuse memoized work if pointers are updated before calling lifecycles

src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
* catches render error in a boundary during full deferred mounting
Expand Down Expand Up @@ -2143,8 +2128,6 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* can reuse side-effects after being preempted, if shouldComponentUpdate is false
* can update a completed tree before it has a chance to commit
* updates a child even though the old props is empty
* can defer side-effects and resume them later on
* can defer side-effects and reuse them later - complex
* deprioritizes setStates that happens within a deprioritized tree
* calls callback after update is flushed
* calls setState callback even if component bails out
Expand All @@ -2153,6 +2136,10 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* invokes ref callbacks after insertion/update/unmount
* supports string refs

src/renderers/shared/fiber/__tests__/ReactIncrementalTriangle-test.js
* renders the triangle demo without inconsistencies
* fuzz tester

src/renderers/shared/fiber/__tests__/ReactIncrementalUpdates-test.js
* applies updates in order of priority
* applies updates with equal priority in insertion order
Expand Down
24 changes: 12 additions & 12 deletions src/renderers/noop/ReactNoopEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,18 +430,18 @@ var ReactNoop = {
if (fiber.updateQueue) {
logUpdateQueue(fiber.updateQueue, depth);
}
const childInProgress = fiber.progressedChild;
if (childInProgress && childInProgress !== fiber.child) {
log(
' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority,
);
logFiber(childInProgress, depth + 1);
if (fiber.child) {
log(' '.repeat(depth + 1) + 'CURRENT');
}
} else if (fiber.child && fiber.updateQueue) {
log(' '.repeat(depth + 1) + 'CHILDREN');
}
// const childInProgress = fiber.progressedChild;
// if (childInProgress && childInProgress !== fiber.child) {
// log(
// ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
// );
// logFiber(childInProgress, depth + 1);
// if (fiber.child) {
// log(' '.repeat(depth + 1) + 'CURRENT');
// }
// } else if (fiber.child && fiber.updateQueue) {
// log(' '.repeat(depth + 1) + 'CHILDREN');
// }
if (fiber.child) {
logFiber(fiber.child, depth + 1);
}
Expand Down
70 changes: 31 additions & 39 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ if (__DEV__) {
}

const {
cloneFiber,
createWorkInProgress,
createFiberFromElement,
createFiberFromFragment,
createFiberFromText,
Expand Down Expand Up @@ -207,12 +207,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
childToDelete = childToDelete.alternate;
}
// Deletions are added in reversed order so we add it to the front.
const last = returnFiber.progressedLastDeletion;
// At this point, the return fiber's effect list is empty except for
// deletions, so we can just append the deletion to the list. The remaining
// effects aren't added until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.progressedLastDeletion = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.progressedFirstDeletion = returnFiber.progressedLastDeletion = childToDelete;
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
Expand Down Expand Up @@ -262,7 +266,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
if (shouldClone) {
const clone = cloneFiber(fiber, priority);
const clone = createWorkInProgress(fiber, priority);
clone.index = 0;
clone.sibling = null;
return clone;
Expand Down Expand Up @@ -1445,44 +1449,32 @@ exports.mountChildFibersInPlace = ChildReconciler(false, false);
exports.cloneChildFibers = function(
current: Fiber | null,
workInProgress: Fiber,
renderPriority: PriorityLevel,
): void {
if (!workInProgress.child) {
invariant(
current === null || workInProgress.child === current.child,
'Resuming work not yet implemented.',
);

if (workInProgress.child === null) {
return;
}
if (current !== null && 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.
let 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.
let newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
workInProgress.child = newChild;

let currentChild = workInProgress.child;
let newChild = createWorkInProgress(currentChild, renderPriority);
// TODO: Pass this as an argument, since it's easy to forget.
newChild.pendingProps = currentChild.pendingProps;
workInProgress.child = newChild;

newChild.return = workInProgress;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(
currentChild,
renderPriority,
);
newChild.pendingProps = currentChild.pendingProps;
newChild.return = workInProgress;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = cloneFiber(
currentChild,
currentChild.pendingWorkPriority,
);
newChild.return = workInProgress;
}
newChild.sibling = null;
} else {
// 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 !== null) {
child.return = workInProgress;
child = child.sibling;
}
}
newChild.sibling = null;
};
131 changes: 54 additions & 77 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ var {NoContext} = require('ReactTypeOfInternalContext');

var {NoEffect} = require('ReactTypeOfSideEffect');

var {cloneUpdateQueue} = require('ReactFiberUpdateQueue');

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

if (__DEV__) {
Expand Down Expand Up @@ -118,7 +116,6 @@ export type Fiber = {|

// Input is the data coming into process this fiber. Arguments. Props.
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 state updates and callbacks.
Expand Down Expand Up @@ -150,24 +147,6 @@ export type 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 | null,

// When we reconcile children onto progressedChild it is possible that we have
// to delete some child fibers. We need to keep track of this side-effects so
// that if we continue later on, we have to include those effects. Deletions
// are added in the reverse order from sibling pointers.
progressedFirstDeletion: Fiber | null,
progressedLastDeletion: Fiber | null,

// 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.
Expand Down Expand Up @@ -234,10 +213,6 @@ var createFiber = function(
lastEffect: null,

pendingWorkPriority: NoWork,
progressedPriority: NoWork,
progressedChild: null,
progressedFirstDeletion: null,
progressedLastDeletion: null,

alternate: null,
};
Expand All @@ -260,66 +235,61 @@ 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,
exports.createWorkInProgress = function(
current: Fiber,
renderPriority: 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
// objects for things that are never updated. It also allow us to reclaim the
// extra memory if needed.
let alt = fiber.alternate;
if (alt !== null) {
// If we clone, then we do so from the "current" state. The current state
// can't have any side-effects that are still valid so we reset just to be
// sure.
alt.effectTag = NoEffect;
alt.nextEffect = null;
alt.firstEffect = null;
alt.lastEffect = null;
} else {
// This should not have an alternate already
alt = createFiber(fiber.tag, fiber.key, fiber.internalContextTag);
alt.type = fiber.type;
let workInProgress = current.alternate;
if (workInProgress === 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 objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
current.key,
current.internalContextTag,
);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;

alt.progressedChild = fiber.progressedChild;
alt.progressedPriority = fiber.progressedPriority;
if (__DEV__) {
// DEV-only fields
workInProgress._debugID = current._debugID;
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
}

alt.alternate = fiber;
fiber.alternate = alt;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoWork;

// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
}

alt.stateNode = fiber.stateNode;
alt.child = fiber.child;
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
alt.index = fiber.index; // This should always be overridden.
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;
cloneUpdateQueue(fiber, alt);
alt.pendingWorkPriority = priorityLevel;
workInProgress.pendingWorkPriority = renderPriority;

alt.memoizedProps = fiber.memoizedProps;
alt.memoizedState = fiber.memoizedState;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;

if (__DEV__) {
alt._debugID = fiber._debugID;
alt._debugSource = fiber._debugSource;
alt._debugOwner = fiber._debugOwner;
}
// pendingProps is set by the parent during reconciliation.
// TODO: Pass this as an argument.

return alt;
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;

return workInProgress;
};

exports.createHostRootFiber = function(): Fiber {
Expand Down Expand Up @@ -481,3 +451,10 @@ exports.createFiberFromPortal = function(
};
return fiber;
};

exports.largerPriority = function(
p1: PriorityLevel,
p2: PriorityLevel,
): PriorityLevel {
return p1 !== NoWork && (p2 === NoWork || p2 > p1) ? p1 : p2;
};
Loading