Skip to content

Commit

Permalink
Sierpinski Triangle Demo
Browse files Browse the repository at this point in the history
With some hacky fixes to work around some minor bugs which needs
to be fixed properly later.
  • Loading branch information
sebmarkbage committed Jul 6, 2016
1 parent e1ff540 commit eb99f4d
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 35 deletions.
110 changes: 98 additions & 12 deletions examples/fiber/index.html
Expand Up @@ -19,26 +19,112 @@ <h1>Fiber Example</h1>
</div>
<script src="../../build/react.js"></script>
<script src="../../build/react-dom-fiber.js"></script>
<script>
function ExampleApplication(props) {
var elapsed = Math.round(props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
var message =
'React has been successfully running for ' + seconds + ' seconds.';
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.min.js"></script>
<script type="text/babel">

var dotStyle = {
position: 'absolute',
background: '#61dafb',
font: 'normal 15px sans-serif',
textAlign: 'center',
cursor: 'pointer',
};

var containerStyle = {
position: 'absolute',
transformOrigin: '0 0',
left: '50%',
top: '50%',
};

var targetSize = 25;

function updateSelection(idx) {
// TODO
}

function Dot(props) {
var s = props.size;
var idx = props.idx;
var style = {
...dotStyle,
width: s + 'px',
height: s + 'px',
left: (props.x) + 'px',
top: (props.y) + 'px',
borderRadius: (s / 2) + 'px',
lineHeight: (s) + 'px',
// background: idx === hoveredIdx ? '#ff0' : dotStyle.background
};
return (
<div style={style} onMouseEnter={() => updateSelection(idx)}>
{props.text}
</div>
);
}

function SierpinskiTriangle({ x, y, s, idx, depth, hover, children }) {
if (s <= targetSize) {
return (
<Dot
x={x - (targetSize / 2)}
y={y - (targetSize / 2)}
size={targetSize}
text={children}
idx={idx}
hover={hover}
/>
);
}
var newSize = s / 2;
// Artificially long execution time.
s = 0;
while (s < 100000) {
s++;
}
if (s > newSize) {
s = newSize;
}

return React.DOM.p(null, message);
return [
<SierpinskiTriangle x={x} y={y - (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
<SierpinskiTriangle x={x - s} y={y + (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
<SierpinskiTriangle x={x + s} y={y + (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
];
}

// Call React.createFactory instead of directly call ExampleApplication({...}) in React.render
var ExampleApplicationFactory = React.createFactory(ExampleApplication);
var cachedTriangle = null;

function ExampleApplication(props) {
var seconds = (props.elapsed / 1000) % 10;
var scale = 1 + (seconds > 5 ? 10 - seconds : seconds) / 10;
var transform = 'scale(' + (scale / 2.1) + ') translateZ(0.1px)';
var lowPriChildren = true; // change this to false to see the effect

return (
<div hidden={lowPriChildren} style={{ ...containerStyle, transform }}>
<SierpinskiTriangle x={0} y={0} s={1000} idx={0}>
{'' + Math.floor(seconds)}
</SierpinskiTriangle>
</div>
);
}

var start = new Date().getTime();
setInterval(function() {
function update() {
ReactDOMFiber.render(
ExampleApplicationFactory({elapsed: new Date().getTime() - start}),
<ExampleApplication elapsed={new Date().getTime() - start} />,
document.getElementById('container')
);
}, 50);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
</script>
</body>
</html>
45 changes: 42 additions & 3 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Expand Up @@ -39,32 +39,71 @@ function recursivelyAppendChildren(parent : Element, child : HostChildren<Instan
}
}

var COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];

var DOMRenderer = ReactFiberReconciler({

updateContainer(container : Container, children : HostChildren<Instance>) : void {
if (container.firstChild === children && container.lastChild === children) {
// Rudimentary bail out mechanism.
return;
}
container.innerHTML = '';
recursivelyAppendChildren(container, children);
},

createInstance(type : string, props : Props, children : HostChildren<Instance>) : Instance {
const domElement = document.createElement(type);
recursivelyAppendChildren(domElement, children);
if (typeof props.style === 'object') {
Object.assign(domElement.style, props.style);
}
if (typeof props.onMouseEnter === 'function') {
domElement.addEventListener('mouseenter', props.onMouseEnter);
}
if (typeof props.onMouseLeave === 'function') {
domElement.addEventListener('mouseleave', props.onMouseLeave);
}
if (typeof props.children === 'string') {
domElement.textContent = props.children;
return domElement;
}
domElement.innerHTML = '';
recursivelyAppendChildren(domElement, children);
return domElement;
},

prepareUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : boolean {
/*
Visualize the reconciliation
*/
/*
if (typeof newProps.children === 'string') {
var c = +newProps.children;
if (!isNaN(c)) {
domElement.style.background = COLORS[c];
}
}
*/
return true;
},

commitUpdate(domElement : Instance, oldProps : Props, newProps : Props, children : HostChildren<Instance>) : void {
domElement.innerHTML = '';
recursivelyAppendChildren(domElement, children);
if (typeof newProps.style === 'object') {
Object.assign(domElement.style, newProps.style);
}
if (typeof newProps.children === 'string') {
domElement.textContent = newProps.children;
return;
}
if (children && (domElement.firstChild === children || domElement.firstChild === children.output)) {
// Rudimentary bail out mechanism.
return;
}
if (domElement.firstChild) {
return;
}
domElement.innerHTML = '';
recursivelyAppendChildren(domElement, children);
},

deleteInstance(instance : Instance) : void {
Expand Down
1 change: 1 addition & 0 deletions src/renderers/shared/fiber/ReactFiber.js
Expand Up @@ -188,6 +188,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
alt.child = fiber.child;
alt.sibling = fiber.sibling;
alt.ref = alt.ref;
alt.pendingProps = fiber.pendingProps;
alt.pendingWorkPriority = priorityLevel;

alt.alternate = fiber;
Expand Down
48 changes: 41 additions & 7 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Expand Up @@ -49,14 +49,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
function updateFunctionalComponent(current, workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.pendingProps;
console.log('update fn:', fn.name);
// console.log('update fn:', fn.name);
var nextChildren = fn(props);
reconcileChildren(current, workInProgress, nextChildren);
workInProgress.pendingWorkPriority = NoWork;
}

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

var nextChildren = workInProgress.pendingProps.children;

Expand Down Expand Up @@ -90,14 +90,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
var props = workInProgress.pendingProps;
var value = fn(props);
if (typeof value === 'object' && value && typeof value.render === 'function') {
console.log('performed work on class:', fn.name);
// console.log('performed work on class:', fn.name);
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
if (workInProgress.alternate) {
workInProgress.alternate.tag = ClassComponent;
}
} else {
console.log('performed work on fn:', fn.name);
// console.log('performed work on fn:', fn.name);
// Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent;
if (workInProgress.alternate) {
Expand All @@ -113,7 +113,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
if (!coroutine) {
throw new Error('Should be resolved by now');
}
console.log('begin coroutine', workInProgress.type.name);
// console.log('begin coroutine', workInProgress.type.name);
reconcileChildren(current, workInProgress, coroutine.children);
workInProgress.pendingWorkPriority = NoWork;
}
Expand All @@ -138,7 +138,32 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
} while (child = child.sibling);
}

function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
let child = firstChild;
do {
// Ensure that the first and last effect of the parent corresponds
// to the children's first and last effect.
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = child.firstEffect;
}
if (child.lastEffect) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = child.firstEffect;
}
returnFiber.lastEffect = child.lastEffect;
}
} while (child = child.sibling);
}

var shallowEqual = require('shallowEqual');

function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
if (current && current.memoizedProps && shallowEqual(current.memoizedProps, workInProgress.pendingProps)) {
workInProgress.pendingProps = current.memoizedProps;
} else if (workInProgress.memoizedProps && shallowEqual(workInProgress.memoizedProps, workInProgress.pendingProps)) {
workInProgress.pendingProps = workInProgress.memoizedProps;
}

// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
Expand All @@ -153,13 +178,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
workInProgress.pendingProps = null;
workInProgress.pendingWorkPriority = NoWork;
workInProgress.stateNode = current.stateNode;

if (current.child) {
// If we bail out but still has work with the current priority in this
// subtree, we need to go find it right now. If we don't, we won't flush
// it until the next tick.
workInProgress.child = current.child;
reuseChildren(workInProgress, workInProgress.child);
if (workInProgress.pendingWorkPriority <= priorityLevel) {
if (workInProgress.pendingWorkPriority !== 0 && workInProgress.pendingWorkPriority <= priorityLevel) {
// TODO: This passes the current node and reads the priority level and
// pending props from that. We want it to read our priority level and
// pending props from the work in progress. Needs restructuring.
Expand All @@ -180,6 +206,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
// ping-pong update scenario, this version could already be what we're
// looking for. In that case, we should be able to just bail out.
workInProgress.pendingProps = null;
workInProgress.firstEffect = null;
workInProgress.nextEffect = null;
workInProgress.lastEffect = null;
if (workInProgress.child && workInProgress.child.alternate) {
workInProgress.child = workInProgress.child.alternate;
reuseChildrenEffects(workInProgress, workInProgress.child);
}

// TODO: We should be able to bail out if there is remaining work at a lower
// priority too. However, I don't know if that is safe or even better since
// the other tree could've potentially finished that work.
Expand All @@ -196,7 +230,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
updateFunctionalComponent(current, workInProgress);
return workInProgress.child;
case ClassComponent:
console.log('class component', workInProgress.pendingProps.type.name);
// console.log('class component', workInProgress.pendingProps.type.name);
return workInProgress.child;
case HostContainer:
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
Expand Down
20 changes: 14 additions & 6 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Expand Up @@ -36,6 +36,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
const prepareUpdate = config.prepareUpdate;

function markForPreEffect(workInProgress : Fiber) {
workInProgress.hasEffect = true;
// Schedule a side-effect on this fiber, BEFORE the children's side-effects.
if (workInProgress.firstEffect) {
workInProgress.nextEffect = workInProgress.firstEffect;
Expand All @@ -47,6 +48,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
}

/*
// TODO: It's possible this will create layout thrash issues because mutations
// of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
// of a sibling reads, then the next sibling updates and reads etc.
function markForPostEffect(workInProgress : Fiber) {
// Schedule a side-effect on this fiber, AFTER the children's side-effects.
if (workInProgress.lastEffect) {
Expand Down Expand Up @@ -125,11 +129,11 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
switch (workInProgress.tag) {
case FunctionalComponent:
console.log('/functional component', workInProgress.type.name);
// console.log('/functional component', workInProgress.type.name);
transferOutput(workInProgress.child, workInProgress);
return null;
case ClassComponent:
console.log('/class component', workInProgress.type.name);
// console.log('/class component', workInProgress.type.name);
transferOutput(workInProgress.child, workInProgress);
return null;
case HostContainer:
Expand All @@ -142,11 +146,15 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
markForPreEffect(workInProgress);
return null;
case HostComponent:
console.log('/host component', workInProgress.type);
// console.log('/host component', workInProgress.type);
const child = workInProgress.child;
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
const newProps = workInProgress.pendingProps;
workInProgress.memoizedProps = newProps;
let newProps = workInProgress.pendingProps;
if (newProps) {
workInProgress.memoizedProps = newProps;
} else {
newProps = workInProgress.memoizedProps = current.memoizedProps;
}
if (current && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
Expand All @@ -165,7 +173,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
}
return null;
case CoroutineComponent:
console.log('/coroutine component', workInProgress.pendingProps.handler.name);
// console.log('/coroutine component', workInProgress.pendingProps.handler.name);
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
transferOutput(workInProgress.stateNode, workInProgress);
Expand Down

0 comments on commit eb99f4d

Please sign in to comment.