Skip to content

Commit

Permalink
[Fiber] Animation priority work (#7466)
Browse files Browse the repository at this point in the history
* High priority work

Adds the ability to schedule and perform high priority work. In the
noop renderer, this is exposed using a method `performHighPriWork(fn)`
where the function is executed and all updates in that scope are given
high priority.

To do this, the scheduler keeps track of a default priority level.
A new function `performWithPriority(priority, fn)` changes the default
priority before calling the function, then resets it afterwards.

* Rename overloaded priority terms

"High" and "low" priority are overloaded terms. There are priority
levels called HighPriority and LowPriority. Meanwhile, there are
functions called {perform,schedule}HighPriWork, which corresponds
to requestAnimationFrame, and {perform,schedule}LowPriWork, which
corresponds to requestIdleCallback. But in fact, work that has
HighPriority is meant to be scheduled with requestIdleCallback.
This is super confusing.

To address this, {perform,schedule}HighPriWork has been renamed
to {perform,schedule}AnimationWork, and
{perform,schedule}LowPriWork has been renamed to
{perform,schedule}DeferredWork. HighPriority and LowPriority
remain the same.

* Priority levels merge fix
  • Loading branch information
acdlite authored and sebmarkbage committed Sep 13, 2016
1 parent 3e54b28 commit 6144212
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 70 deletions.
4 changes: 2 additions & 2 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ var DOMRenderer = ReactFiberReconciler({
// Noop
},

scheduleHighPriCallback: window.requestAnimationFrame,
scheduleAnimationCallback: window.requestAnimationFrame,

scheduleLowPriCallback: window.requestIdleCallback,
scheduleDeferredCallback: window.requestIdleCallback,

});

Expand Down
35 changes: 21 additions & 14 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ import type { UpdateQueue } from 'ReactFiberUpdateQueue';
import type { HostChildren } from 'ReactFiberReconciler';

var ReactFiberReconciler = require('ReactFiberReconciler');
var {
AnimationPriority,
} = require('ReactPriorityLevel');

var scheduledHighPriCallback = null;
var scheduledLowPriCallback = null;
var scheduledAnimationCallback = null;
var scheduledDeferredCallback = null;

const TERMINAL_TAG = 99;

Expand Down Expand Up @@ -88,12 +91,12 @@ var NoopRenderer = ReactFiberReconciler({
deleteInstance(instance : Instance) : void {
},

scheduleHighPriCallback(callback) {
scheduledHighPriCallback = callback;
scheduleAnimationCallback(callback) {
scheduledAnimationCallback = callback;
},

scheduleLowPriCallback(callback) {
scheduledLowPriCallback = callback;
scheduleDeferredCallback(callback) {
scheduledDeferredCallback = callback;
},

});
Expand All @@ -114,21 +117,21 @@ var ReactNoop = {
}
},

flushHighPri() {
var cb = scheduledHighPriCallback;
flushAnimationPri() {
var cb = scheduledAnimationCallback;
if (cb === null) {
return;
}
scheduledHighPriCallback = null;
scheduledAnimationCallback = null;
cb();
},

flushLowPri(timeout : number = Infinity) {
var cb = scheduledLowPriCallback;
flushDeferredPri(timeout : number = Infinity) {
var cb = scheduledDeferredCallback;
if (cb === null) {
return;
}
scheduledLowPriCallback = null;
scheduledDeferredCallback = null;
var timeRemaining = timeout;
cb({
timeRemaining() {
Expand All @@ -143,8 +146,12 @@ var ReactNoop = {
},

flush() {
ReactNoop.flushHighPri();
ReactNoop.flushLowPri();
ReactNoop.flushAnimationPri();
ReactNoop.flushDeferredPri();
},

performAnimationWork(fn: Function) {
NoopRenderer.performWithPriority(AnimationPriority, fn);
},

// Logs the current state of the tree.
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
}

function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void {
const { scheduleLowPriWork } = getScheduler();
const { scheduleDeferredWork } = getScheduler();
fiber.updateQueue = updateQueue;
// Schedule update on the alternate as well, since we don't know which tree
// is current.
Expand All @@ -138,7 +138,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
// Duck type root
if (fiber.stateNode && fiber.stateNode.containerInfo) {
const root : FiberRoot = (fiber.stateNode : any);
scheduleLowPriWork(root, priorityLevel);
scheduleDeferredWork(root, priorityLevel);
return;
}
if (!fiber.return) {
Expand Down
23 changes: 10 additions & 13 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';

var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');

var {
LowPriority,
} = require('ReactPriorityLevel');

type Deadline = {
timeRemaining : () => number
};
Expand All @@ -44,8 +41,8 @@ export type HostConfig<T, P, I, C> = {
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : void,
deleteInstance(instance : I) : void,

scheduleHighPriCallback(callback : () => void) : void,
scheduleLowPriCallback(callback : (deadline : Deadline) => void) : void
scheduleAnimationCallback(callback : () => void) : void,
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void

};

Expand All @@ -55,14 +52,15 @@ export type Reconciler<C> = {
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
unmountContainer(container : OpaqueNode) : void,
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,

// Used to extract the return value from the initial render. Legacy API.
getPublicRootInstance(container : OpaqueNode) : (C | null),
};

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconciler<C> {

var { scheduleLowPriWork } = ReactFiberScheduler(config);
var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);

return {

Expand All @@ -73,9 +71,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconci
// TODO: This should not override the pendingWorkPriority if there is
// higher priority work in the subtree.
container.pendingProps = element;
container.pendingWorkPriority = LowPriority;

scheduleLowPriWork(root, LowPriority);
scheduleWork(root);

// It may seem strange that we don't return the root here, but that will
// allow us to have containers that are in the middle of the tree instead
Expand All @@ -88,21 +85,21 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconci
const root : FiberRoot = (container.stateNode : any);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = element;
root.current.pendingWorkPriority = LowPriority;

scheduleLowPriWork(root, LowPriority);
scheduleWork(root);
},

unmountContainer(container : OpaqueNode) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = [];
root.current.pendingWorkPriority = LowPriority;

scheduleLowPriWork(root, LowPriority);
scheduleWork(root);
},

performWithPriority,

getPublicRootInstance(container : OpaqueNode) : (C | null) {
return null;
},
Expand Down
99 changes: 85 additions & 14 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ var { cloneFiber } = require('ReactFiber');

var {
NoWork,
LowPriority,
AnimationPriority,
SynchronousPriority,
} = require('ReactPriorityLevel');

var timeHeuristicForUnitOfWork = 1;

export type Scheduler = {
scheduleLowPriWork: (root : FiberRoot, priority : PriorityLevel) => void
scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void
};

module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
Expand All @@ -45,8 +48,11 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
const { completeWork } = ReactFiberCompleteWork(config);
const { commitWork } = ReactFiberCommitWork(config);

// const scheduleHighPriCallback = config.scheduleHighPriCallback;
const scheduleLowPriCallback = config.scheduleLowPriCallback;
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;
Expand Down Expand Up @@ -217,7 +223,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
}
}

function performLowPriWork(deadline) {
function performDeferredWork(deadline) {
if (!nextUnitOfWork) {
nextUnitOfWork = findNextUnitOfWork();
}
Expand All @@ -229,20 +235,68 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
nextUnitOfWork = findNextUnitOfWork();
}
} else {
scheduleLowPriCallback(performLowPriWork);
scheduleDeferredCallback(performDeferredWork);
return;
}
}
}

function scheduleLowPriWork(root : FiberRoot, priority : PriorityLevel) {
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;
Expand All @@ -256,22 +310,39 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
// We're the only work scheduled.
nextScheduledRoot = root;
lastScheduledRoot = root;
scheduleLowPriCallback(performLowPriWork);
scheduleAnimationCallback(performAnimationWork);
}
}

/*
function performHighPriWork() {
// There is no such thing as high pri work yet.
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 ensureHighPriIsScheduled() {
scheduleHighPriCallback(performHighPriWork);
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
const previousDefaultPriority = defaultPriority;
defaultPriority = priorityLevel;
try {
fn();
} finally {
defaultPriority = previousDefaultPriority;
}
}
*/

scheduler = {
scheduleLowPriWork: scheduleLowPriWork,
scheduleWork: scheduleWork,
scheduleDeferredWork: scheduleDeferredWork,
performWithPriority: performWithPriority,
};
return scheduler;
};
Loading

0 comments on commit 6144212

Please sign in to comment.