aboutsummaryrefslogtreecommitdiff
path: root/node_modules/react-dom/lib/ReactFiberScheduler.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/react-dom/lib/ReactFiberScheduler.js')
-rw-r--r--node_modules/react-dom/lib/ReactFiberScheduler.js331
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