@samitouri / QOS-React / commits / f84a8eabc7

Fiber side-effects

This adds tracking of side-effects that gets scheduled during an update. As the tree gets reconciled, the side-effectful fibers are linked together in an ordered singly linked list. That way we can walk the linked list to commit only the work that needs to be synchronous - quickly. We also store first and last nodes within a fiber. That way when we reuse an already processed subtree, we can reuse that subset of the linked list.

Sebastian Markbage committed Jun 29, 2016 at 15:20 UTC f84a8eabc7618de81dac4bb2b645f85303dc430d
4 files changed +111 -5
src/renderers/shared/fiber/ReactFiber.js
+21
@@ -82,6 +82,16 @@ export type Fiber = Instance & {
82 // if this returns multiple values. Such as a fragment.
83 output: any, // This type will be more specific once we overload the tag.
84
85 + // Singly linked list fast path to the next fiber with side-effects.
86 + nextEffect: ?Fiber,
87 +
88 + // The first and last fiber with side-effect within this subtree. This allows
89 + // us to reuse a slice of the linked list when we reuse the work done within
90 + // this fiber.
91 + firstEffect: ?Fiber,
92 + lastEffect: ?Fiber,
93 +
94 +
95 // This will be used to quickly determine if a subtree has no pending changes.
96 pendingWorkPriority: PriorityLevel,
97
@@ -129,6 +139,10 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
139 memoizedProps: null,
140 output: null,
141
142 + nextEffect: null,
143 + firstEffect: null,
144 + lastEffect: null,
145 +
146 pendingWorkPriority: NoWork,
147
148 hasWorkInProgress: false,
@@ -157,6 +171,13 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
171 alt.ref = alt.ref;
172 alt.pendingProps = fiber.pendingProps;
173 alt.pendingWorkPriority = priorityLevel;
174 +
175 + // Whenever we clone, we do so to get a new work in progress.
176 + // This ensures that we've reset these in the new tree.
177 + alt.nextEffect = null;
178 + alt.firstEffect = null;
179 + alt.lastEffect = null;
180 +
181 return alt;
182 }
183
src/renderers/shared/fiber/ReactFiberCommitWork.js new
+38
@@ -0,0 +1,38 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactFiberCommitWork
10 + * @flow
11 + */
12 +
13 +'use strict';
14 +
15 +import type { Fiber } from 'ReactFiber';
16 +
17 +var ReactTypeOfWork = require('ReactTypeOfWork');
18 +var {
19 + ClassComponent,
20 + HostContainer,
21 + HostComponent,
22 +} = ReactTypeOfWork;
23 +
24 +exports.commitWork = function(finishedWork : Fiber) : void {
25 + switch (finishedWork.tag) {
26 + case ClassComponent:
27 + // TODO: Fire componentDidMount/componentDidUpdate, update refs
28 + return;
29 + case HostContainer:
30 + // TODO: Attach children to root container.
31 + return;
32 + case HostComponent:
33 + console.log('commit updates to host component', finishedWork.type);
34 + return;
35 + default:
36 + throw new Error('This unit of work tag should not have side-effects.');
37 + }
38 +};
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+15
@@ -30,6 +30,16 @@ var {
30 YieldComponent,
31 } = ReactTypeOfWork;
32
33 +function markForPostEffect(workInProgress : Fiber) {
34 + // Schedule a side-effect on this fiber, after the children's side-effects.
35 + if (workInProgress.lastEffect) {
36 + workInProgress.lastEffect.nextEffect = workInProgress;
37 + } else {
38 + workInProgress.firstEffect = workInProgress;
39 + }
40 + workInProgress.lastEffect = workInProgress;
41 +}
42 +
43 function transferOutput(child : ?Fiber, returnFiber : Fiber) {
44 // If we have a single result, we just pass that through as the output to
45 // avoid unnecessary traversal. When we have multiple output, we just pass
@@ -108,6 +118,11 @@ exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fib
118 return null;
119 case HostComponent:
120 transferOutput(workInProgress.child, workInProgress);
121 + if (workInProgress.alternate) {
122 + // If we have an alternate, that means this is an update and we need to
123 + // schedule a side-effect to do the updates.
124 + markForPostEffect(workInProgress);
125 + }
126 console.log('/host component', workInProgress.type);
127 return null;
128 case CoroutineComponent:
src/renderers/shared/fiber/ReactFiberScheduler.js
+37 -5
@@ -19,6 +19,7 @@ import type { HostConfig } from 'ReactFiberReconciler';
19 var { cloneFiber } = require('ReactFiber');
20 var { beginWork } = require('ReactFiberBeginWork');
21 var { completeWork } = require('ReactFiberCompleteWork');
22 +var { commitWork } = require('ReactFiberCommitWork');
23 var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork');
24
25 var {
@@ -78,6 +79,22 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) {
79 return null;
80 }
81
82 + function commitAllWork(finishedWork : Fiber) {
83 + // Commit all the side-effects within a tree.
84 + // TODO: Error handling.
85 + let effectfulFiber = finishedWork.firstEffect;
86 + while (effectfulFiber) {
87 + commitWork(effectfulFiber);
88 + const next = effectfulFiber.nextEffect;
89 + // Ensure that we clean these up so that we don't accidentally keep them.
90 + // I'm not actually sure this matters because we can't reset firstEffect
91 + // and lastEffect since they're on every node, not just the effectful
92 + // ones. So we have to clean everything as we reuse nodes anyway.
93 + effectfulFiber.nextEffect = null;
94 + effectfulFiber = next;
95 + }
96 + }
97 +
98 function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
99 while (true) {
100 // The current, flushed, state of this fiber is the alternate.
@@ -96,11 +113,25 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) {
113
114 const returnFiber = workInProgress.return;
115
99 - // Ensure that remaining work priority bubbles up.
100 - if (returnFiber && workInProgress.pendingWorkPriority !== NoWork &&
101 - (returnFiber.pendingWorkPriority === NoWork ||
102 - returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
103 - returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority;
116 + if (returnFiber) {
117 + // Ensure that remaining work priority bubbles up.
118 + if (workInProgress.pendingWorkPriority !== NoWork &&
119 + (returnFiber.pendingWorkPriority === NoWork ||
120 + returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
121 + returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority;
122 + }
123 + // Ensure that the first and last effect of the parent corresponds
124 + // to the children's first and last effect. This probably relies on
125 + // children completing in order.
126 + if (!returnFiber.firstEffect) {
127 + returnFiber.firstEffect = workInProgress.firstEffect;
128 + }
129 + if (workInProgress.lastEffect) {
130 + if (returnFiber.lastEffect) {
131 + returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
132 + }
133 + returnFiber.lastEffect = workInProgress.lastEffect;
134 + }
135 }
136
137 if (next) {
@@ -121,6 +152,7 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) {
152 // also ensures that work scheduled during reconciliation gets deferred.
153 // const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
154 console.log('----- COMPLETED with remaining work:', workInProgress.pendingWorkPriority);
155 + commitAllWork(workInProgress);
156 const nextWork = findNextUnitOfWork();
157 // if (!nextWork && hasMoreWork) {
158 // TODO: This can happen when some deep work completes and we don't