UNPKG

11.7 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 *
8 */
9
10'use strict';
11
12var ReactFiberBeginWork = require('./ReactFiberBeginWork');
13var ReactFiberCompleteWork = require('./ReactFiberCompleteWork');
14var ReactFiberCommitWork = require('./ReactFiberCommitWork');
15
16var _require = require('./ReactFiber'),
17 cloneFiber = _require.cloneFiber;
18
19var _require2 = require('./ReactPriorityLevel'),
20 NoWork = _require2.NoWork,
21 LowPriority = _require2.LowPriority,
22 AnimationPriority = _require2.AnimationPriority,
23 SynchronousPriority = _require2.SynchronousPriority;
24
25var timeHeuristicForUnitOfWork = 1;
26
27module.exports = function (config) {
28 // Use a closure to circumvent the circular dependency between the scheduler
29 // and ReactFiberBeginWork. Don't know if there's a better way to do this.
30 var scheduler = void 0;
31 function getScheduler() {
32 return scheduler;
33 }
34
35 var _ReactFiberBeginWork = ReactFiberBeginWork(config, getScheduler),
36 beginWork = _ReactFiberBeginWork.beginWork;
37
38 var _ReactFiberCompleteWo = ReactFiberCompleteWork(config),
39 completeWork = _ReactFiberCompleteWo.completeWork;
40
41 var _ReactFiberCommitWork = ReactFiberCommitWork(config),
42 commitWork = _ReactFiberCommitWork.commitWork;
43
44 var scheduleAnimationCallback = config.scheduleAnimationCallback;
45 var scheduleDeferredCallback = config.scheduleDeferredCallback;
46
47 // The default priority to use for updates.
48 var defaultPriority = LowPriority;
49
50 // The next work in progress fiber that we're currently working on.
51 var nextUnitOfWork = null;
52 var nextPriorityLevel = NoWork;
53
54 // Linked list of roots with scheduled work on them.
55 var nextScheduledRoot = null;
56 var lastScheduledRoot = null;
57
58 function findNextUnitOfWork() {
59 // Clear out roots with no more work on them.
60 while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
61 nextScheduledRoot.isScheduled = false;
62 if (nextScheduledRoot === lastScheduledRoot) {
63 nextScheduledRoot = null;
64 lastScheduledRoot = null;
65 nextPriorityLevel = NoWork;
66 return null;
67 }
68 nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
69 }
70 // TODO: This is scanning one root at a time. It should be scanning all
71 // roots for high priority work before moving on to lower priorities.
72 var root = nextScheduledRoot;
73 var highestPriorityRoot = null;
74 var highestPriorityLevel = NoWork;
75 while (root) {
76 if (highestPriorityLevel === NoWork || highestPriorityLevel > root.current.pendingWorkPriority) {
77 highestPriorityLevel = root.current.pendingWorkPriority;
78 highestPriorityRoot = root;
79 }
80 // We didn't find anything to do in this root, so let's try the next one.
81 root = root.nextScheduledRoot;
82 }
83 if (highestPriorityRoot) {
84 nextPriorityLevel = highestPriorityLevel;
85 return cloneFiber(highestPriorityRoot.current, highestPriorityLevel);
86 }
87
88 nextPriorityLevel = NoWork;
89 return null;
90 }
91
92 function commitAllWork(finishedWork) {
93 // Commit all the side-effects within a tree.
94 // TODO: Error handling.
95 var effectfulFiber = finishedWork.firstEffect;
96 while (effectfulFiber) {
97 var current = effectfulFiber.alternate;
98 commitWork(current, effectfulFiber);
99 var next = effectfulFiber.nextEffect;
100 // Ensure that we clean these up so that we don't accidentally keep them.
101 // I'm not actually sure this matters because we can't reset firstEffect
102 // and lastEffect since they're on every node, not just the effectful
103 // ones. So we have to clean everything as we reuse nodes anyway.
104 effectfulFiber.nextEffect = null;
105 effectfulFiber = next;
106 }
107 }
108
109 function resetWorkPriority(workInProgress) {
110 var newPriority = NoWork;
111 // progressedChild is going to be the child set with the highest priority.
112 // Either it is the same as child, or it just bailed out because it choose
113 // not to do the work.
114 var child = workInProgress.progressedChild;
115 while (child) {
116 // Ensure that remaining work priority bubbles up.
117 if (child.pendingWorkPriority !== NoWork && (newPriority === NoWork || newPriority > child.pendingWorkPriority)) {
118 newPriority = child.pendingWorkPriority;
119 }
120 child = child.sibling;
121 }
122 workInProgress.pendingWorkPriority = newPriority;
123 }
124
125 function completeUnitOfWork(workInProgress) {
126 while (true) {
127 // The current, flushed, state of this fiber is the alternate.
128 // Ideally nothing should rely on this, but relying on it here
129 // means that we don't need an additional field on the work in
130 // progress.
131 var current = workInProgress.alternate;
132 var next = completeWork(current, workInProgress);
133
134 resetWorkPriority(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 workInProgress.updateQueue = null;
140
141 var returnFiber = workInProgress['return'];
142
143 if (returnFiber) {
144 // Ensure that the first and last effect of the parent corresponds
145 // to the children's first and last effect. This probably relies on
146 // children completing in order.
147 if (!returnFiber.firstEffect) {
148 returnFiber.firstEffect = workInProgress.firstEffect;
149 }
150 if (workInProgress.lastEffect) {
151 if (returnFiber.lastEffect) {
152 returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
153 }
154 returnFiber.lastEffect = workInProgress.lastEffect;
155 }
156 }
157
158 if (next) {
159 // If completing this work spawned new work, do that next.
160 return next;
161 } else if (workInProgress.sibling) {
162 // If there is more work to do in this returnFiber, do that next.
163 return workInProgress.sibling;
164 } else if (returnFiber) {
165 // If there's no more work in this returnFiber. Complete the returnFiber.
166 workInProgress = returnFiber;
167 continue;
168 } else {
169 // If we're at the root, there's no more work to do. We can flush it.
170 var _root = workInProgress.stateNode;
171 if (_root.current === workInProgress) {
172 throw new Error('Cannot commit the same tree as before. This is probably a bug ' + 'related to the return field.');
173 }
174 _root.current = workInProgress;
175 // TODO: We can be smarter here and only look for more work in the
176 // "next" scheduled work since we've already scanned passed. That
177 // also ensures that work scheduled during reconciliation gets deferred.
178 // const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
179 commitAllWork(workInProgress);
180 var nextWork = findNextUnitOfWork();
181 // if (!nextWork && hasMoreWork) {
182 // TODO: This can happen when some deep work completes and we don't
183 // know if this was the last one. We should be able to keep track of
184 // the highest priority still in the tree for one pass. But if we
185 // terminate an update we don't know.
186 // throw new Error('FiberRoots should not have flagged more work if there is none.');
187 // }
188 return nextWork;
189 }
190 }
191 }
192
193 function performUnitOfWork(workInProgress) {
194 // The current, flushed, state of this fiber is the alternate.
195 // Ideally nothing should rely on this, but relying on it here
196 // means that we don't need an additional field on the work in
197 // progress.
198 var current = workInProgress.alternate;
199 var next = beginWork(current, workInProgress, nextPriorityLevel);
200
201 if (next) {
202 // If this spawns new work, do that next.
203 return next;
204 } else {
205 // Otherwise, complete the current work.
206 return completeUnitOfWork(workInProgress);
207 }
208 }
209
210 function performDeferredWork(deadline) {
211 if (!nextUnitOfWork) {
212 nextUnitOfWork = findNextUnitOfWork();
213 }
214 while (nextUnitOfWork) {
215 if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
216 nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
217 if (!nextUnitOfWork) {
218 // Find more work. We might have time to complete some more.
219 nextUnitOfWork = findNextUnitOfWork();
220 }
221 } else {
222 scheduleDeferredCallback(performDeferredWork);
223 return;
224 }
225 }
226 }
227
228 function scheduleDeferredWork(root, priority) {
229 // We must reset the current unit of work pointer so that we restart the
230 // search from the root during the next tick, in case there is now higher
231 // priority work somewhere earlier than before.
232 if (priority <= nextPriorityLevel) {
233 nextUnitOfWork = null;
234 }
235
236 // Set the priority on the root, without deprioritizing
237 if (root.current.pendingWorkPriority === NoWork || priority <= root.current.pendingWorkPriority) {
238 root.current.pendingWorkPriority = priority;
239 }
240
241 if (root.isScheduled) {
242 // If we're already scheduled, we can bail out.
243 return;
244 }
245 root.isScheduled = true;
246 if (lastScheduledRoot) {
247 // Schedule ourselves to the end.
248 lastScheduledRoot.nextScheduledRoot = root;
249 lastScheduledRoot = root;
250 } else {
251 // We're the only work scheduled.
252 nextScheduledRoot = root;
253 lastScheduledRoot = root;
254 scheduleDeferredCallback(performDeferredWork);
255 }
256 }
257
258 function performAnimationWork() {
259 // Always start from the root
260 nextUnitOfWork = findNextUnitOfWork();
261 while (nextUnitOfWork && nextPriorityLevel !== NoWork) {
262 nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
263 if (!nextUnitOfWork) {
264 // Keep searching for animation work until there's no more left
265 nextUnitOfWork = findNextUnitOfWork();
266 }
267 // Stop if the next unit of work is low priority
268 if (nextPriorityLevel > AnimationPriority) {
269 scheduleDeferredCallback(performDeferredWork);
270 return;
271 }
272 }
273 }
274
275 function scheduleAnimationWork(root, priorityLevel) {
276 // Set the priority on the root, without deprioritizing
277 if (root.current.pendingWorkPriority === NoWork || priorityLevel <= root.current.pendingWorkPriority) {
278 root.current.pendingWorkPriority = priorityLevel;
279 }
280
281 if (root.isScheduled) {
282 // If we're already scheduled, we can bail out.
283 return;
284 }
285 root.isScheduled = true;
286 if (lastScheduledRoot) {
287 // Schedule ourselves to the end.
288 lastScheduledRoot.nextScheduledRoot = root;
289 lastScheduledRoot = root;
290 } else {
291 // We're the only work scheduled.
292 nextScheduledRoot = root;
293 lastScheduledRoot = root;
294 scheduleAnimationCallback(performAnimationWork);
295 }
296 }
297
298 function scheduleWork(root) {
299 if (defaultPriority === SynchronousPriority) {
300 throw new Error('Not implemented yet');
301 }
302
303 if (defaultPriority === NoWork) {
304 return;
305 }
306 if (defaultPriority > AnimationPriority) {
307 scheduleDeferredWork(root, defaultPriority);
308 return;
309 }
310 scheduleAnimationWork(root, defaultPriority);
311 }
312
313 function performWithPriority(priorityLevel, fn) {
314 var previousDefaultPriority = defaultPriority;
315 defaultPriority = priorityLevel;
316 try {
317 fn();
318 } finally {
319 defaultPriority = previousDefaultPriority;
320 }
321 }
322
323 scheduler = {
324 scheduleWork: scheduleWork,
325 scheduleDeferredWork: scheduleDeferredWork,
326 performWithPriority: performWithPriority
327 };
328 return scheduler;
329};
\No newline at end of file