Skip to content

Commit

Permalink
Merge pull request facebook#6981 from sebmarkbage/newreconciler
Browse files Browse the repository at this point in the history
[Fiber] Add support for simple updates and fiber pooling
  • Loading branch information
sebmarkbage committed Jun 7, 2016
2 parents eb705d1 + 3a32d26 commit ccd26ee
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 84 deletions.
37 changes: 34 additions & 3 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,26 @@ var {
var ReactFiber = require('ReactFiber');
var ReactReifiedYield = require('ReactReifiedYield');

function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChildren) : Fiber {
function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
if (typeof newChildren !== 'object' || newChildren === null) {
return previousSibling;
}

switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
const element = (newChildren : ReactElement<any>);
if (nextReusable &&
element.type === nextReusable.type &&
element.key === nextReusable.key) {
// TODO: This is not sufficient since previous siblings could be new.
// Will fix reconciliation properly later.
const clone = ReactFiber.cloneFiber(nextReusable);
clone.input = element.props;
clone.child = nextReusable.child;
clone.sibling = null;
previousSibling.sibling = clone;
return clone;
}
const child = ReactFiber.createFiberFromElement(element);
previousSibling.sibling = child;
child.parent = parent;
Expand Down Expand Up @@ -64,7 +76,11 @@ function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChild
if (Array.isArray(newChildren)) {
let prev : Fiber = previousSibling;
for (var i = 0; i < newChildren.length; i++) {
prev = createSubsequentChild(parent, prev, newChildren[i]);
let reusable = null;
if (prev.alternate) {
reusable = prev.alternate.sibling;
}
prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
}
return prev;
} else {
Expand All @@ -81,6 +97,17 @@ function createFirstChild(parent, newChildren) {
switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
const element = (newChildren : ReactElement<any>);
const existingChild : ?Fiber = parent.child;
if (existingChild &&
element.type === existingChild.type &&
element.key === existingChild.key) {
// Get the clone of the existing fiber.
const clone = ReactFiber.cloneFiber(existingChild);
clone.input = element.props;
clone.child = existingChild.child;
clone.sibling = null;
return clone;
}
const child = ReactFiber.createFiberFromElement(element);
child.parent = parent;
return child;
Expand Down Expand Up @@ -114,7 +141,11 @@ function createFirstChild(parent, newChildren) {
prev = createFirstChild(parent, newChildren[i]);
first = prev;
} else {
prev = createSubsequentChild(parent, prev, newChildren[i]);
let reusable = null;
if (prev.alternate) {
reusable = prev.alternate.sibling;
}
prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
}
}
return first;
Expand Down
80 changes: 69 additions & 11 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,59 +25,90 @@ var ReactElement = require('ReactElement');

import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';

export type Fiber = {
// An Instance is shared between all versions of a component. We can easily
// break this out into a separate object to avoid copying so much to the
// alternate versions of the tree. We put this on a single object for now to
// minimize the number of objects created during the initial render.
type Instance = {

// Tag identifying the type of fiber.
tag: number,

// Singly Linked List Tree Structure.
parent: ?Fiber, // Consider a regenerated temporary parent stack instead.
child: ?Fiber,
sibling: ?Fiber,
// The parent Fiber used to create this one. The type is constrained to the
// Instance part of the Fiber since it is not safe to traverse the tree from
// the instance.
parent: ?Instance, // Consider a regenerated temporary parent stack instead.

// Unique identifier of this child.
key: ?string,
key: null | string,

// The function/class/module associated with this fiber.
type: any,

// The local state associated with this fiber.
stateNode: ?Object,

};

// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
export type Fiber = Instance & {

// Singly Linked List Tree Structure.
child: ?Fiber,
sibling: ?Fiber,

// The ref last used to attach this node.
// I'll avoid adding an owner field for prod and model that as functions.
ref: null | (handle : ?Object) => void,

// Input is the data coming into process this fiber. Arguments. Props.
input: any, // This type will be more specific once we overload the tag.
// TODO: I think that there is a way to merge input and memoizedInput somehow.
memoizedInput: any, // The input used to create the output.
// 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.

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

// The local state associated with this fiber.
stateNode: ?Object,
// 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,

};

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

// Instance

tag: tag,

parent: null,
child: null,
sibling: null,

key: key,

type: null,

stateNode: null,

// Fiber

child: null,
sibling: null,

ref: null,

input: null,
memoizedInput: null,
output: null,

hasPendingChanges: true,

stateNode: null,
alternate: null,

};
};
Expand All @@ -86,6 +117,33 @@ function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
}

// This is used to create an alternate fiber to do work on.
exports.cloneFiber = function(fiber : Fiber) : Fiber {
// 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.
if (fiber.alternate) {
return fiber.alternate;
}
// This should not have an alternate already
var alt = createFiber(fiber.tag, fiber.key);

if (fiber.parent) {
// TODO: This assumes the parent's alternate is already created.
// Stop using the alternates of parents once we have a parent stack.
// $FlowFixMe: This downcast is not safe. It is intentionally an error.
alt.parent = fiber.parent.alternate;
}

alt.type = fiber.type;
alt.stateNode = fiber.stateNode;
alt.alternate = fiber;
fiber.alternate = alt;
return alt;
};

exports.createFiberFromElement = function(element : ReactElement) {
const fiber = exports.createFiberFromElementType(element.type, element.key);
fiber.input = element.props;
Expand Down
96 changes: 55 additions & 41 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,100 +27,114 @@ var {
YieldComponent,
} = ReactTypesOfWork;

function updateFunctionalComponent(unitOfWork) {
var fn = unitOfWork.type;
var props = unitOfWork.input;
console.log('perform work on:', fn.name);
function updateFunctionalComponent(workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.input;
console.log('update fn:', fn.name);
var nextChildren = fn(props);

unitOfWork.child = ReactChildFiber.reconcileChildFibers(
unitOfWork,
unitOfWork.child,
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren
);
}

function updateHostComponent(unitOfWork) {
console.log('host component', unitOfWork.type, typeof unitOfWork.input.children === 'string' ? unitOfWork.input.children : '');
function updateHostComponent(workInProgress) {
console.log('host component', workInProgress.type, typeof workInProgress.input.children === 'string' ? workInProgress.input.children : '');

var nextChildren = unitOfWork.input.children;
unitOfWork.child = ReactChildFiber.reconcileChildFibers(
unitOfWork,
unitOfWork.child,
var nextChildren = workInProgress.input.children;
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren
);
}

function mountIndeterminateComponent(unitOfWork) {
var fn = unitOfWork.type;
var props = unitOfWork.input;
function mountIndeterminateComponent(workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.input;
var value = fn(props);
if (typeof value === 'object' && value && typeof value.render === 'function') {
console.log('performed work on class:', fn.name);
// Proceed under the assumption that this is a class instance
unitOfWork.tag = ClassComponent;
workInProgress.tag = ClassComponent;
} else {
console.log('performed work on fn:', fn.name);
// Proceed under the assumption that this is a functional component
unitOfWork.tag = FunctionalComponent;
workInProgress.tag = FunctionalComponent;
}
unitOfWork.child = ReactChildFiber.reconcileChildFibers(
unitOfWork,
unitOfWork.child,
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
value
);
}

function updateCoroutineComponent(unitOfWork) {
var coroutine = (unitOfWork.input : ?ReactCoroutine);
function updateCoroutineComponent(workInProgress) {
var coroutine = (workInProgress.input : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
}
console.log('begin coroutine', unitOfWork.type.name);
unitOfWork.child = ReactChildFiber.reconcileChildFibers(
unitOfWork,
unitOfWork.child,
console.log('begin coroutine', workInProgress.type.name);
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
coroutine.children
);
}

function beginWork(unitOfWork : Fiber) : ?Fiber {
switch (unitOfWork.tag) {
function beginWork(workInProgress : Fiber) : ?Fiber {
const alt = workInProgress.alternate;
if (alt && workInProgress.input === alt.memoizedInput) {
// The most likely scenario is that the previous copy of the tree contains
// the same input as the new one. In that case, we can just copy the output
// and children from that node.
workInProgress.output = alt.output;
workInProgress.child = alt.child;
return null;
}
if (workInProgress.input === workInProgress.memoizedInput) {
// In a ping-pong scenario, this version could actually contain the
// old input. In that case, we can just bail out.
return null;
}
switch (workInProgress.tag) {
case IndeterminateComponent:
mountIndeterminateComponent(unitOfWork);
mountIndeterminateComponent(workInProgress);
break;
case FunctionalComponent:
updateFunctionalComponent(unitOfWork);
updateFunctionalComponent(workInProgress);
break;
case ClassComponent:
console.log('class component', unitOfWork.input.type.name);
console.log('class component', workInProgress.input.type.name);
break;
case HostComponent:
updateHostComponent(unitOfWork);
updateHostComponent(workInProgress);
break;
case CoroutineHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
unitOfWork.tag = CoroutineComponent;
workInProgress.tag = CoroutineComponent;
// Intentionally fall through since this is now the same.
case CoroutineComponent:
updateCoroutineComponent(unitOfWork);
updateCoroutineComponent(workInProgress);
// This doesn't take arbitrary time so we could synchronously just begin
// eagerly do the work of unitOfWork.child as an optimization.
if (unitOfWork.child) {
return beginWork(unitOfWork.child);
// eagerly do the work of workInProgress.child as an optimization.
if (workInProgress.child) {
return beginWork(workInProgress.child);
}
break;
case YieldComponent:
// A yield component is just a placeholder, we can just run through the
// next one immediately.
if (unitOfWork.sibling) {
return beginWork(unitOfWork.sibling);
if (workInProgress.sibling) {
return beginWork(workInProgress.sibling);
}
return null;
default:
throw new Error('Unknown unit of work tag');
}
return unitOfWork.child;
return workInProgress.child;
}

exports.beginWork = beginWork;
Loading

0 comments on commit ccd26ee

Please sign in to comment.