@samitouri / QOS-React / commits / 986c63c6d4

Extract scheduler into its own module

The scheduler is getting quite complicated. I'll extract it into its own module.

Sebastian Markbage committed Jun 15, 2016 at 16:30 UTC 986c63c6d4a808f0f3108bb9610803c11f04ed47
2 files changed +257 -225
src/renderers/shared/fiber/ReactFiberReconciler.js
+2 -225
@@ -13,19 +13,13 @@
13 'use strict';
14
15 import type { Fiber } from 'ReactFiber';
16 -import type { PriorityLevel } from 'ReactPriorityLevel';
16 import type { FiberRoot } from 'ReactFiberRoot';
17
19 -var ReactFiber = require('ReactFiber');
20 -var { beginWork } = require('ReactFiberBeginWork');
21 -var { completeWork } = require('ReactFiberCompleteWork');
18 var { createFiberRoot } = require('ReactFiberRoot');
19 +var ReactFiberScheduler = require('ReactFiberScheduler');
20
21 var {
25 - NoWork,
26 - HighPriority,
22 LowPriority,
28 - OffscreenPriority,
23 } = require('ReactPriorityLevel');
24
25 type ReactHostElement<T, P> = {
@@ -37,8 +31,6 @@ type Deadline = {
31 timeRemaining : () => number
32 };
33
40 -var timeHeuristicForUnitOfWork = 1;
41 -
34 export type HostConfig<T, P, I> = {
35
36 createHostInstance(element : ReactHostElement<T, P>) : I,
@@ -60,222 +52,7 @@ export type Reconciler = {
52
53 module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
54
63 - // const scheduleHighPriCallback = config.scheduleHighPriCallback;
64 - const scheduleLowPriCallback = config.scheduleLowPriCallback;
65 -
66 - // The next work in progress fiber that we're currently working on.
67 - let nextUnitOfWork : ?Fiber = null;
68 -
69 - // Linked list of roots with scheduled work on them.
70 - let nextScheduledRoot : ?FiberRoot = null;
71 - let lastScheduledRoot : ?FiberRoot = null;
72 -
73 - function findNextUnitOfWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) : ?Fiber {
74 - let current = root.current;
75 - while (current) {
76 - if (current.pendingWorkPriority !== 0 &&
77 - current.pendingWorkPriority <= priorityLevel) {
78 - // This node has work to do that fits our priority level criteria.
79 - if (current.pendingProps !== null) {
80 - // We found some work to do. We need to return the "work in progress"
81 - // of this node which will be the alternate.
82 - const clone = ReactFiber.cloneFiber(current, current.pendingWorkPriority);
83 - clone.pendingProps = current.pendingProps;
84 - return clone;
85 - }
86 - // If we have a child let's see if any of our children has work to do.
87 - // Only bother doing this at all if the current priority level matches
88 - // because it is the highest priority for the whole subtree.
89 - // TODO: Coroutines can have work in their stateNode which is another
90 - // type of child that needs to be searched for work.
91 - if (current.child) {
92 - // Ensure we have a work in progress copy to backtrack through.
93 - ReactFiber.cloneFiber(current, NoWork);
94 - current = current.child;
95 - continue;
96 - }
97 - // If we match the priority but has no child and no work to do,
98 - // then we can safely reset the flag.
99 - current.pendingWorkPriority = NoWork;
100 - }
101 - while (!current.sibling) {
102 - // TODO: Stop using parent here. See below.
103 - // $FlowFixMe: This downcast is not safe. It is intentionally an error.
104 - current = current.parent;
105 - if (!current) {
106 - return null;
107 - }
108 - if (current.pendingWorkPriority <= priorityLevel) {
109 - // If this subtree had work left to do, we would have returned it by
110 - // now. This could happen if a child with pending work gets cleaned up
111 - // but we don't clear the flag then. It is safe to reset it now.
112 - current.pendingWorkPriority = NoWork;
113 - }
114 - }
115 - current = current.sibling;
116 - }
117 - return null;
118 - }
119 -
120 - function findNextUnitOfWork() {
121 - // Clear out roots with no more work on them.
122 - while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
123 - nextScheduledRoot.isScheduled = false;
124 - if (nextScheduledRoot === lastScheduledRoot) {
125 - nextScheduledRoot = null;
126 - lastScheduledRoot = null;
127 - return null;
128 - }
129 - nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
130 - }
131 - let root = nextScheduledRoot;
132 - while (root) {
133 - // Find the highest possible priority work to do.
134 - // This loop is unrolled just to satisfy Flow's enum constraint.
135 - // We could make arbitrary many idle priority levels but having
136 - // too many just means flushing changes too often.
137 - let work = findNextUnitOfWorkAtPriority(root, HighPriority);
138 - if (work) {
139 - return work;
140 - }
141 - work = findNextUnitOfWorkAtPriority(root, LowPriority);
142 - if (work) {
143 - return work;
144 - }
145 - work = findNextUnitOfWorkAtPriority(root, OffscreenPriority);
146 - if (work) {
147 - return work;
148 - }
149 - // We didn't find anything to do in this root, so let's try the next one.
150 - root = root.nextScheduledRoot;
151 - }
152 - return null;
153 - }
154 -
155 - function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
156 - while (true) {
157 - // The current, flushed, state of this fiber is the alternate.
158 - // Ideally nothing should rely on this, but relying on it here
159 - // means that we don't need an additional field on the work in
160 - // progress.
161 - const current = workInProgress.alternate;
162 - const next = completeWork(current, workInProgress);
163 -
164 - // The work is now done. We don't need this anymore. This flags
165 - // to the system not to redo any work here.
166 - workInProgress.pendingProps = null;
167 -
168 - // TODO: Stop using the parent for this purpose. I think this will break
169 - // down in edge cases because when nodes are reused during bailouts, we
170 - // don't know which of two parents was used. Instead we should maintain
171 - // a temporary manual stack.
172 - // $FlowFixMe: This downcast is not safe. It is intentionally an error.
173 - const parent = workInProgress.parent;
174 -
175 - // Ensure that remaining work priority bubbles up.
176 - if (parent && workInProgress.pendingWorkPriority !== NoWork &&
177 - (parent.pendingWorkPriority === NoWork ||
178 - parent.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
179 - parent.pendingWorkPriority = workInProgress.pendingWorkPriority;
180 - }
181 -
182 - if (next) {
183 - // If completing this work spawned new work, do that next.
184 - return next;
185 - } else if (workInProgress.sibling) {
186 - // If there is more work to do in this parent, do that next.
187 - return workInProgress.sibling;
188 - } else if (parent) {
189 - // If there's no more work in this parent. Complete the parent.
190 - workInProgress = parent;
191 - } else {
192 - // If we're at the root, there's no more work to do. We can flush it.
193 - const root : FiberRoot = (workInProgress.stateNode : any);
194 - root.current = workInProgress;
195 - console.log('completed one root flush with remaining work at priority', workInProgress.pendingWorkPriority);
196 - const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
197 - // TODO: We can be smarter here and only look for more work in the
198 - // "next" scheduled work since we've already scanned passed. That
199 - // also ensures that work scheduled during reconciliation gets deferred.
200 - const nextWork = findNextUnitOfWork();
201 - if (!nextWork && hasMoreWork) {
202 - throw new Error('FiberRoots should not have flagged more work if there is none.');
203 - }
204 - return nextWork;
205 - }
206 - }
207 - }
208 -
209 - function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
210 - // Ignore work if there is nothing to do.
211 - if (workInProgress.pendingProps === null) {
212 - return null;
213 - }
214 - // The current, flushed, state of this fiber is the alternate.
215 - // Ideally nothing should rely on this, but relying on it here
216 - // means that we don't need an additional field on the work in
217 - // progress.
218 - const current = workInProgress.alternate;
219 - const next = beginWork(current, workInProgress);
220 - if (next) {
221 - // If this spawns new work, do that next.
222 - return next;
223 - } else {
224 - // Otherwise, complete the current work.
225 - return completeUnitOfWork(workInProgress);
226 - }
227 - }
228 -
229 - function performLowPriWork(deadline : Deadline) {
230 - if (!nextUnitOfWork) {
231 - nextUnitOfWork = findNextUnitOfWork();
232 - }
233 - while (nextUnitOfWork) {
234 - if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
235 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
236 - if (!nextUnitOfWork) {
237 - // Find more work. We might have time to complete some more.
238 - nextUnitOfWork = findNextUnitOfWork();
239 - }
240 - } else {
241 - scheduleLowPriCallback(performLowPriWork);
242 - return;
243 - }
244 - }
245 - }
246 -
247 - function scheduleLowPriWork(root : FiberRoot) {
248 - // We must reset the current unit of work pointer so that we restart the
249 - // search from the root during the next tick, in case there is now higher
250 - // priority work somewhere earlier than before.
251 - nextUnitOfWork = null;
252 -
253 - if (root.isScheduled) {
254 - // If we're already scheduled, we can bail out.
255 - return;
256 - }
257 - root.isScheduled = true;
258 - if (lastScheduledRoot) {
259 - // Schedule ourselves to the end.
260 - lastScheduledRoot.nextScheduledRoot = root;
261 - lastScheduledRoot = root;
262 - } else {
263 - // We're the only work scheduled.
264 - nextScheduledRoot = root;
265 - lastScheduledRoot = root;
266 - scheduleLowPriCallback(performLowPriWork);
267 - }
268 - }
269 -
270 - /*
271 - function performHighPriWork() {
272 - // There is no such thing as high pri work yet.
273 - }
274 -
275 - function ensureHighPriIsScheduled() {
276 - scheduleHighPriCallback(performHighPriWork);
277 - }
278 - */
55 + var { scheduleLowPriWork } = ReactFiberScheduler(config);
56
57 return {
58
src/renderers/shared/fiber/ReactFiberScheduler.js new
+255
@@ -0,0 +1,255 @@
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 ReactFiberScheduler
10 + * @flow
11 + */
12 +
13 +'use strict';
14 +
15 +import type { Fiber } from 'ReactFiber';
16 +import type { PriorityLevel } from 'ReactPriorityLevel';
17 +import type { FiberRoot } from 'ReactFiberRoot';
18 +import type { HostConfig } from 'ReactFiberReconciler';
19 +
20 +var ReactFiber = require('ReactFiber');
21 +var { beginWork } = require('ReactFiberBeginWork');
22 +var { completeWork } = require('ReactFiberCompleteWork');
23 +
24 +var {
25 + NoWork,
26 + HighPriority,
27 + LowPriority,
28 + OffscreenPriority,
29 +} = require('ReactPriorityLevel');
30 +
31 +var timeHeuristicForUnitOfWork = 1;
32 +
33 +module.exports = function<T, P, I>(config : HostConfig<T, P, I>) {
34 +
35 + // const scheduleHighPriCallback = config.scheduleHighPriCallback;
36 + const scheduleLowPriCallback = config.scheduleLowPriCallback;
37 +
38 + // The next work in progress fiber that we're currently working on.
39 + let nextUnitOfWork : ?Fiber = null;
40 +
41 + // Linked list of roots with scheduled work on them.
42 + let nextScheduledRoot : ?FiberRoot = null;
43 + let lastScheduledRoot : ?FiberRoot = null;
44 +
45 + function findNextUnitOfWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) : ?Fiber {
46 + let current = root.current;
47 + while (current) {
48 + if (current.pendingWorkPriority !== 0 &&
49 + current.pendingWorkPriority <= priorityLevel) {
50 + // This node has work to do that fits our priority level criteria.
51 + if (current.pendingProps !== null) {
52 + // We found some work to do. We need to return the "work in progress"
53 + // of this node which will be the alternate.
54 + const clone = ReactFiber.cloneFiber(current, current.pendingWorkPriority);
55 + clone.pendingProps = current.pendingProps;
56 + return clone;
57 + }
58 + // If we have a child let's see if any of our children has work to do.
59 + // Only bother doing this at all if the current priority level matches
60 + // because it is the highest priority for the whole subtree.
61 + // TODO: Coroutines can have work in their stateNode which is another
62 + // type of child that needs to be searched for work.
63 + if (current.child) {
64 + // Ensure we have a work in progress copy to backtrack through.
65 + ReactFiber.cloneFiber(current, NoWork);
66 + current = current.child;
67 + continue;
68 + }
69 + // If we match the priority but has no child and no work to do,
70 + // then we can safely reset the flag.
71 + current.pendingWorkPriority = NoWork;
72 + }
73 + while (!current.sibling) {
74 + // TODO: Stop using parent here. See below.
75 + // $FlowFixMe: This downcast is not safe. It is intentionally an error.
76 + current = current.parent;
77 + if (!current) {
78 + return null;
79 + }
80 + if (current.pendingWorkPriority <= priorityLevel) {
81 + // If this subtree had work left to do, we would have returned it by
82 + // now. This could happen if a child with pending work gets cleaned up
83 + // but we don't clear the flag then. It is safe to reset it now.
84 + current.pendingWorkPriority = NoWork;
85 + }
86 + }
87 + current = current.sibling;
88 + }
89 + return null;
90 + }
91 +
92 + function findNextUnitOfWork() {
93 + // Clear out roots with no more work on them.
94 + while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
95 + nextScheduledRoot.isScheduled = false;
96 + if (nextScheduledRoot === lastScheduledRoot) {
97 + nextScheduledRoot = null;
98 + lastScheduledRoot = null;
99 + return null;
100 + }
101 + nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
102 + }
103 + let root = nextScheduledRoot;
104 + while (root) {
105 + // Find the highest possible priority work to do.
106 + // This loop is unrolled just to satisfy Flow's enum constraint.
107 + // We could make arbitrary many idle priority levels but having
108 + // too many just means flushing changes too often.
109 + let work = findNextUnitOfWorkAtPriority(root, HighPriority);
110 + if (work) {
111 + return work;
112 + }
113 + work = findNextUnitOfWorkAtPriority(root, LowPriority);
114 + if (work) {
115 + return work;
116 + }
117 + work = findNextUnitOfWorkAtPriority(root, OffscreenPriority);
118 + if (work) {
119 + return work;
120 + }
121 + // We didn't find anything to do in this root, so let's try the next one.
122 + root = root.nextScheduledRoot;
123 + }
124 + return null;
125 + }
126 +
127 + function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
128 + while (true) {
129 + // The current, flushed, state of this fiber is the alternate.
130 + // Ideally nothing should rely on this, but relying on it here
131 + // means that we don't need an additional field on the work in
132 + // progress.
133 + const current = workInProgress.alternate;
134 + const next = completeWork(current, workInProgress);
135 +
136 + // The work is now done. We don't need this anymore. This flags
137 + // to the system not to redo any work here.
138 + workInProgress.pendingProps = null;
139 +
140 + // TODO: Stop using the parent for this purpose. I think this will break
141 + // down in edge cases because when nodes are reused during bailouts, we
142 + // don't know which of two parents was used. Instead we should maintain
143 + // a temporary manual stack.
144 + // $FlowFixMe: This downcast is not safe. It is intentionally an error.
145 + const parent = workInProgress.parent;
146 +
147 + // Ensure that remaining work priority bubbles up.
148 + if (parent && workInProgress.pendingWorkPriority !== NoWork &&
149 + (parent.pendingWorkPriority === NoWork ||
150 + parent.pendingWorkPriority > workInProgress.pendingWorkPriority)) {
151 + parent.pendingWorkPriority = workInProgress.pendingWorkPriority;
152 + }
153 +
154 + if (next) {
155 + // If completing this work spawned new work, do that next.
156 + return next;
157 + } else if (workInProgress.sibling) {
158 + // If there is more work to do in this parent, do that next.
159 + return workInProgress.sibling;
160 + } else if (parent) {
161 + // If there's no more work in this parent. Complete the parent.
162 + workInProgress = parent;
163 + } else {
164 + // If we're at the root, there's no more work to do. We can flush it.
165 + const root : FiberRoot = (workInProgress.stateNode : any);
166 + root.current = workInProgress;
167 + console.log('completed one root flush with remaining work at priority', workInProgress.pendingWorkPriority);
168 + const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
169 + // TODO: We can be smarter here and only look for more work in the
170 + // "next" scheduled work since we've already scanned passed. That
171 + // also ensures that work scheduled during reconciliation gets deferred.
172 + const nextWork = findNextUnitOfWork();
173 + if (!nextWork && hasMoreWork) {
174 + throw new Error('FiberRoots should not have flagged more work if there is none.');
175 + }
176 + return nextWork;
177 + }
178 + }
179 + }
180 +
181 + function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
182 + // Ignore work if there is nothing to do.
183 + if (workInProgress.pendingProps === null) {
184 + return null;
185 + }
186 + // The current, flushed, state of this fiber is the alternate.
187 + // Ideally nothing should rely on this, but relying on it here
188 + // means that we don't need an additional field on the work in
189 + // progress.
190 + const current = workInProgress.alternate;
191 + const next = beginWork(current, workInProgress);
192 + if (next) {
193 + // If this spawns new work, do that next.
194 + return next;
195 + } else {
196 + // Otherwise, complete the current work.
197 + return completeUnitOfWork(workInProgress);
198 + }
199 + }
200 +
201 + function performLowPriWork(deadline) {
202 + if (!nextUnitOfWork) {
203 + nextUnitOfWork = findNextUnitOfWork();
204 + }
205 + while (nextUnitOfWork) {
206 + if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
207 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
208 + if (!nextUnitOfWork) {
209 + // Find more work. We might have time to complete some more.
210 + nextUnitOfWork = findNextUnitOfWork();
211 + }
212 + } else {
213 + scheduleLowPriCallback(performLowPriWork);
214 + return;
215 + }
216 + }
217 + }
218 +
219 + function scheduleLowPriWork(root : FiberRoot) {
220 + // We must reset the current unit of work pointer so that we restart the
221 + // search from the root during the next tick, in case there is now higher
222 + // priority work somewhere earlier than before.
223 + nextUnitOfWork = null;
224 +
225 + if (root.isScheduled) {
226 + // If we're already scheduled, we can bail out.
227 + return;
228 + }
229 + root.isScheduled = true;
230 + if (lastScheduledRoot) {
231 + // Schedule ourselves to the end.
232 + lastScheduledRoot.nextScheduledRoot = root;
233 + lastScheduledRoot = root;
234 + } else {
235 + // We're the only work scheduled.
236 + nextScheduledRoot = root;
237 + lastScheduledRoot = root;
238 + scheduleLowPriCallback(performLowPriWork);
239 + }
240 + }
241 +
242 + /*
243 + function performHighPriWork() {
244 + // There is no such thing as high pri work yet.
245 + }
246 +
247 + function ensureHighPriIsScheduled() {
248 + scheduleHighPriCallback(performHighPriWork);
249 + }
250 + */
251 +
252 + return {
253 + scheduleLowPriWork: scheduleLowPriWork,
254 + };
255 +};