UNPKG

react-native

Version:

A framework for building native apps using React

349 lines (312 loc) • 12.3 kB
/** * 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. * * @providesModule ReactFiberScheduler * @flow */ 'use strict'; import type { Fiber } from 'ReactFiber'; import type { FiberRoot } from 'ReactFiberRoot'; import type { HostConfig } from 'ReactFiberReconciler'; import type { PriorityLevel } from 'ReactPriorityLevel'; var ReactFiberBeginWork = require('ReactFiberBeginWork'); var ReactFiberCompleteWork = require('ReactFiberCompleteWork'); var ReactFiberCommitWork = require('ReactFiberCommitWork'); var { cloneFiber } = require('ReactFiber'); var { NoWork, LowPriority, AnimationPriority, SynchronousPriority, } = require('ReactPriorityLevel'); var timeHeuristicForUnitOfWork = 1; export type Scheduler = { scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void }; module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) { // 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. let scheduler; function getScheduler(): Scheduler { return scheduler; } const { beginWork } = ReactFiberBeginWork(config, getScheduler); const { completeWork } = ReactFiberCompleteWork(config); const { commitWork } = ReactFiberCommitWork(config); const scheduleAnimationCallback = config.scheduleAnimationCallback; const scheduleDeferredCallback = config.scheduleDeferredCallback; // The default priority to use for updates. let defaultPriority : PriorityLevel = LowPriority; // The next work in progress fiber that we're currently working on. let nextUnitOfWork : ?Fiber = null; let nextPriorityLevel : PriorityLevel = NoWork; // Linked list of roots with scheduled work on them. let nextScheduledRoot : ?FiberRoot = null; let lastScheduledRoot : ?FiberRoot = 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. let root = nextScheduledRoot; let highestPriorityRoot = null; let 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 : Fiber) { // Commit all the side-effects within a tree. // TODO: Error handling. let effectfulFiber = finishedWork.firstEffect; while (effectfulFiber) { const current = effectfulFiber.alternate; commitWork(current, effectfulFiber); const 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 : Fiber) { let 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. let 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 : Fiber) : ?Fiber { 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. const current = workInProgress.alternate; const 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; const 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. const root : FiberRoot = (workInProgress.stateNode : any); 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); const 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 : Fiber) : ?Fiber { // 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. const current = workInProgress.alternate; const 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 : FiberRoot, priority : PriorityLevel) { // 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: FiberRoot, priorityLevel : 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 : FiberRoot) { 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 : PriorityLevel, fn : Function) { const previousDefaultPriority = defaultPriority; defaultPriority = priorityLevel; try { fn(); } finally { defaultPriority = previousDefaultPriority; } } scheduler = { scheduleWork: scheduleWork, scheduleDeferredWork: scheduleDeferredWork, performWithPriority: performWithPriority, }; return scheduler; };