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
+};