diff options
Diffstat (limited to 'node_modules/react-dom/lib/ReactFiberScheduler.js')
-rw-r--r-- | node_modules/react-dom/lib/ReactFiberScheduler.js | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/node_modules/react-dom/lib/ReactFiberScheduler.js b/node_modules/react-dom/lib/ReactFiberScheduler.js new file mode 100644 index 000000000..0b1490608 --- /dev/null +++ b/node_modules/react-dom/lib/ReactFiberScheduler.js @@ -0,0 +1,331 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * + */ + +'use strict'; + +var ReactFiberBeginWork = require('./ReactFiberBeginWork'); +var ReactFiberCompleteWork = require('./ReactFiberCompleteWork'); +var ReactFiberCommitWork = require('./ReactFiberCommitWork'); + +var _require = require('./ReactFiber'), + cloneFiber = _require.cloneFiber; + +var _require2 = require('./ReactPriorityLevel'), + NoWork = _require2.NoWork, + LowPriority = _require2.LowPriority, + AnimationPriority = _require2.AnimationPriority, + SynchronousPriority = _require2.SynchronousPriority; + +var timeHeuristicForUnitOfWork = 1; + +module.exports = function (config) { + // Use a closure to circumvent the circular dependency between the scheduler + // and ReactFiberBeginWork. Don't know if there's a better way to do this. + var scheduler = void 0; + function getScheduler() { + return scheduler; + } + + var _ReactFiberBeginWork = ReactFiberBeginWork(config, getScheduler), + beginWork = _ReactFiberBeginWork.beginWork; + + var _ReactFiberCompleteWo = ReactFiberCompleteWork(config), + completeWork = _ReactFiberCompleteWo.completeWork; + + var _ReactFiberCommitWork = ReactFiberCommitWork(config), + commitWork = _ReactFiberCommitWork.commitWork; + + var scheduleAnimationCallback = config.scheduleAnimationCallback; + var scheduleDeferredCallback = config.scheduleDeferredCallback; + + // The default priority to use for updates. + var defaultPriority = LowPriority; + + // The next work in progress fiber that we're currently working on. + var nextUnitOfWork = null; + var nextPriorityLevel = NoWork; + + // Linked list of roots with scheduled work on them. + var nextScheduledRoot = null; + var lastScheduledRoot = null; + + function findNextUnitOfWork() { + // Clear out roots with no more work on them. + while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) { + nextScheduledRoot.isScheduled = false; + if (nextScheduledRoot === lastScheduledRoot) { + nextScheduledRoot = null; + lastScheduledRoot = null; + nextPriorityLevel = NoWork; + return null; + } + nextScheduledRoot = nextScheduledRoot.nextScheduledRoot; + } + // TODO: This is scanning one root at a time. It should be scanning all + // roots for high priority work before moving on to lower priorities. + var root = nextScheduledRoot; + var highestPriorityRoot = null; + var highestPriorityLevel = NoWork; + while (root) { + if (highestPriorityLevel === NoWork || highestPriorityLevel > root.current.pendingWorkPriority) { + highestPriorityLevel = root.current.pendingWorkPriority; + highestPriorityRoot = root; + } + // We didn't find anything to do in this root, so let's try the next one. + root = root.nextScheduledRoot; + } + if (highestPriorityRoot) { + nextPriorityLevel = highestPriorityLevel; + return cloneFiber(highestPriorityRoot.current, highestPriorityLevel); + } + + nextPriorityLevel = NoWork; + return null; + } + + function commitAllWork(finishedWork) { + // Commit all the side-effects within a tree. + // TODO: Error handling. + var effectfulFiber = finishedWork.firstEffect; + while (effectfulFiber) { + var current = effectfulFiber.alternate; + commitWork(current, effectfulFiber); + var next = effectfulFiber.nextEffect; + // Ensure that we clean these up so that we don't accidentally keep them. + // I'm not actually sure this matters because we can't reset firstEffect + // and lastEffect since they're on every node, not just the effectful + // ones. So we have to clean everything as we reuse nodes anyway. + effectfulFiber.nextEffect = null; + effectfulFiber = next; + } + } + + function resetWorkPriority(workInProgress) { + var newPriority = NoWork; + // progressedChild is going to be the child set with the highest priority. + // Either it is the same as child, or it just bailed out because it choose + // not to do the work. + var child = workInProgress.progressedChild; + while (child) { + // Ensure that remaining work priority bubbles up. + if (child.pendingWorkPriority !== NoWork && (newPriority === NoWork || newPriority > child.pendingWorkPriority)) { + newPriority = child.pendingWorkPriority; + } + child = child.sibling; + } + workInProgress.pendingWorkPriority = newPriority; + } + + function completeUnitOfWork(workInProgress) { + while (true) { + // The current, flushed, state of this fiber is the alternate. + // Ideally nothing should rely on this, but relying on it here + // means that we don't need an additional field on the work in + // progress. + var current = workInProgress.alternate; + var next = completeWork(current, workInProgress); + + resetWorkPriority(workInProgress); + + // The work is now done. We don't need this anymore. This flags + // to the system not to redo any work here. + workInProgress.pendingProps = null; + workInProgress.updateQueue = null; + + var returnFiber = workInProgress['return']; + + if (returnFiber) { + // Ensure that the first and last effect of the parent corresponds + // to the children's first and last effect. This probably relies on + // children completing in order. + if (!returnFiber.firstEffect) { + returnFiber.firstEffect = workInProgress.firstEffect; + } + if (workInProgress.lastEffect) { + if (returnFiber.lastEffect) { + returnFiber.lastEffect.nextEffect = workInProgress.firstEffect; + } + returnFiber.lastEffect = workInProgress.lastEffect; + } + } + + if (next) { + // If completing this work spawned new work, do that next. + return next; + } else if (workInProgress.sibling) { + // If there is more work to do in this returnFiber, do that next. + return workInProgress.sibling; + } else if (returnFiber) { + // If there's no more work in this returnFiber. Complete the returnFiber. + workInProgress = returnFiber; + continue; + } else { + // If we're at the root, there's no more work to do. We can flush it. + var _root = workInProgress.stateNode; + if (_root.current === workInProgress) { + throw new Error('Cannot commit the same tree as before. This is probably a bug ' + 'related to the return field.'); + } + _root.current = workInProgress; + // TODO: We can be smarter here and only look for more work in the + // "next" scheduled work since we've already scanned passed. That + // also ensures that work scheduled during reconciliation gets deferred. + // const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork; + commitAllWork(workInProgress); + var nextWork = findNextUnitOfWork(); + // if (!nextWork && hasMoreWork) { + // TODO: This can happen when some deep work completes and we don't + // know if this was the last one. We should be able to keep track of + // the highest priority still in the tree for one pass. But if we + // terminate an update we don't know. + // throw new Error('FiberRoots should not have flagged more work if there is none.'); + // } + return nextWork; + } + } + } + + function performUnitOfWork(workInProgress) { + // The current, flushed, state of this fiber is the alternate. + // Ideally nothing should rely on this, but relying on it here + // means that we don't need an additional field on the work in + // progress. + var current = workInProgress.alternate; + var next = beginWork(current, workInProgress, nextPriorityLevel); + + if (next) { + // If this spawns new work, do that next. + return next; + } else { + // Otherwise, complete the current work. + return completeUnitOfWork(workInProgress); + } + } + + function performDeferredWork(deadline) { + if (!nextUnitOfWork) { + nextUnitOfWork = findNextUnitOfWork(); + } + while (nextUnitOfWork) { + if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) { + nextUnitOfWork = performUnitOfWork(nextUnitOfWork); + if (!nextUnitOfWork) { + // Find more work. We might have time to complete some more. + nextUnitOfWork = findNextUnitOfWork(); + } + } else { + scheduleDeferredCallback(performDeferredWork); + return; + } + } + } + + function scheduleDeferredWork(root, priority) { + // We must reset the current unit of work pointer so that we restart the + // search from the root during the next tick, in case there is now higher + // priority work somewhere earlier than before. + if (priority <= nextPriorityLevel) { + nextUnitOfWork = null; + } + + // Set the priority on the root, without deprioritizing + if (root.current.pendingWorkPriority === NoWork || priority <= root.current.pendingWorkPriority) { + root.current.pendingWorkPriority = priority; + } + + if (root.isScheduled) { + // If we're already scheduled, we can bail out. + return; + } + root.isScheduled = true; + if (lastScheduledRoot) { + // Schedule ourselves to the end. + lastScheduledRoot.nextScheduledRoot = root; + lastScheduledRoot = root; + } else { + // We're the only work scheduled. + nextScheduledRoot = root; + lastScheduledRoot = root; + scheduleDeferredCallback(performDeferredWork); + } + } + + function performAnimationWork() { + // Always start from the root + nextUnitOfWork = findNextUnitOfWork(); + while (nextUnitOfWork && nextPriorityLevel !== NoWork) { + nextUnitOfWork = performUnitOfWork(nextUnitOfWork); + if (!nextUnitOfWork) { + // Keep searching for animation work until there's no more left + nextUnitOfWork = findNextUnitOfWork(); + } + // Stop if the next unit of work is low priority + if (nextPriorityLevel > AnimationPriority) { + scheduleDeferredCallback(performDeferredWork); + return; + } + } + } + + function scheduleAnimationWork(root, priorityLevel) { + // Set the priority on the root, without deprioritizing + if (root.current.pendingWorkPriority === NoWork || priorityLevel <= root.current.pendingWorkPriority) { + root.current.pendingWorkPriority = priorityLevel; + } + + if (root.isScheduled) { + // If we're already scheduled, we can bail out. + return; + } + root.isScheduled = true; + if (lastScheduledRoot) { + // Schedule ourselves to the end. + lastScheduledRoot.nextScheduledRoot = root; + lastScheduledRoot = root; + } else { + // We're the only work scheduled. + nextScheduledRoot = root; + lastScheduledRoot = root; + scheduleAnimationCallback(performAnimationWork); + } + } + + function scheduleWork(root) { + if (defaultPriority === SynchronousPriority) { + throw new Error('Not implemented yet'); + } + + if (defaultPriority === NoWork) { + return; + } + if (defaultPriority > AnimationPriority) { + scheduleDeferredWork(root, defaultPriority); + return; + } + scheduleAnimationWork(root, defaultPriority); + } + + function performWithPriority(priorityLevel, fn) { + var previousDefaultPriority = defaultPriority; + defaultPriority = priorityLevel; + try { + fn(); + } finally { + defaultPriority = previousDefaultPriority; + } + } + + scheduler = { + scheduleWork: scheduleWork, + scheduleDeferredWork: scheduleDeferredWork, + performWithPriority: performWithPriority + }; + return scheduler; +};
\ No newline at end of file |