UNPKG

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