Skip to content

Commit

Permalink
Use Scheduler to prioritize updates
Browse files Browse the repository at this point in the history
Changes the implementation of syncUpdates, deferredUpdates, and
interactiveUpdates to use runWithPriority, so

This is the minimum integration between Scheduler and React needed to
unblock use of the Scheduler.next API.
  • Loading branch information
acdlite committed Feb 4, 2019
1 parent 16443f7 commit 89b06f8
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 81 deletions.
22 changes: 22 additions & 0 deletions packages/react-cache/src/__tests__/ReactCache-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe('ReactCache', () => {
beforeEach(() => {
jest.resetModules();

let currentPriorityLevel = 3;

jest.mock('scheduler', () => {
let callbacks = [];
return {
Expand All @@ -38,6 +40,26 @@ describe('ReactCache', () => {
callback();
}
},

unstable_ImmediatePriority: 1,
unstable_UserBlockingPriority: 2,
unstable_NormalPriority: 3,
unstable_LowPriority: 4,
unstable_IdlePriority: 5,

unstable_runWithPriority(priorityLevel, fn) {
const prevPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return fn();
} finally {
currentPriorityLevel = prevPriorityLevel;
}
},

unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
},
};
});

Expand Down
152 changes: 71 additions & 81 deletions packages/react-reconciler/src/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ import type {Interaction} from 'scheduler/src/Tracing';
import {
__interactionsRef,
__subscriberRef,
unstable_wrap as Schedule_tracing_wrap,
unstable_wrap as Scheduler_tracing_wrap,
} from 'scheduler/tracing';
import {
unstable_scheduleCallback as Schedule_scheduleCallback,
unstable_cancelCallback as Schedule_cancelCallback,
unstable_scheduleCallback as Scheduler_scheduleCallback,
unstable_cancelCallback as Scheduler_cancelCallback,
unstable_next as Scheduler_next,
unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
unstable_runWithPriority as runWithPriority,
unstable_ImmediatePriority as ImmediatePriority,
unstable_UserBlockingPriority as UserBlockingPriority,
unstable_NormalPriority as NormalPriority,
unstable_LowPriority as LowPriority,
unstable_IdlePriority as IdlePriority,
} from 'scheduler';
import {
invokeGuardedCallback,
Expand Down Expand Up @@ -124,7 +132,7 @@ import {
computeAsyncExpiration,
computeInteractiveExpiration,
} from './ReactFiberExpirationTime';
import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {ConcurrentMode, ProfileMode, NoContext} from './ReactTypeOfMode';
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
import {createCapturedValue} from './ReactCapturedValue';
import {
Expand Down Expand Up @@ -244,11 +252,6 @@ if (__DEV__) {
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
let lastUniqueAsyncExpiration: number = Sync - 1;

// Represents the expiration time that incoming updates should use. (If this
// is NoWork, use the default strategy: async updates in async mode, sync
// updates in sync mode.)
let expirationContext: ExpirationTime = NoWork;

let isWorking: boolean = false;

// The next work in progress fiber that we're currently working on.
Expand Down Expand Up @@ -588,7 +591,7 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {

function flushPassiveEffects() {
if (passiveEffectCallback !== null) {
Schedule_cancelCallback(passiveEffectCallbackHandle);
Scheduler_cancelCallback(passiveEffectCallbackHandle);
// We call the scheduled callback instead of commitPassiveEffects directly
// to ensure tracing works correctly.
passiveEffectCallback();
Expand Down Expand Up @@ -793,9 +796,11 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
// TODO: Avoid this extra callback by mutating the tracing ref directly,
// like we do at the beginning of commitRoot. I've opted not to do that
// here because that code is still in flux.
callback = Schedule_tracing_wrap(callback);
callback = Scheduler_tracing_wrap(callback);
}
passiveEffectCallbackHandle = Schedule_scheduleCallback(callback);
passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
return Scheduler_scheduleCallback(callback);
});
passiveEffectCallback = callback;
}

Expand Down Expand Up @@ -1579,52 +1584,58 @@ function computeUniqueAsyncExpiration(): ExpirationTime {
}

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
const priorityLevel = getCurrentPriorityLevel();

let expirationTime;
if (expirationContext !== NoWork) {
// An explicit expiration context was set;
expirationTime = expirationContext;
} else if (isWorking) {
if (isCommitting) {
// Updates that occur during the commit phase should have sync priority
// by default.
expirationTime = Sync;
} else {
// Updates during the render phase should expire at the same time as
// the work that is being rendered.
expirationTime = nextRenderExpirationTime;
}
if ((fiber.mode & ConcurrentMode) === NoContext) {
// Outside of concurrent mode, updates are always synchronous.
expirationTime = Sync;
} else if (isWorking && !isCommitting) {
// During render phase, updates expire during as the current render.
expirationTime = nextRenderExpirationTime;
} else {
// No explicit expiration context was set, and we're not currently
// performing work. Calculate a new expiration time.
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) {
// This is an interactive update
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = Sync;
break;
case UserBlockingPriority:
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// This is an async update
break;
case NormalPriority:
// This is a normal, concurrent update
expirationTime = computeAsyncExpiration(currentTime);
}
// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
} else {
// This is a sync update
expirationTime = Sync;
break;
case LowPriority:
case IdlePriority:
expirationTime = Never;
break;
default:
invariant(
false,
'Unknown priority level. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
if (isBatchingInteractiveUpdates) {
// This is an interactive update. Keep track of the lowest pending
// interactive expiration time. This allows us to synchronously flush
// all interactive updates when needed.
if (
lowestPriorityPendingInteractiveExpirationTime === NoWork ||
expirationTime < lowestPriorityPendingInteractiveExpirationTime
) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;

// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
}

// Keep track of the lowest pending interactive expiration time. This
// allows us to synchronously flush all interactive updates
// when needed.
// TODO: Move this to renderer?
if (
priorityLevel === UserBlockingPriority &&
(lowestPriorityPendingInteractiveExpirationTime === NoWork ||
expirationTime < lowestPriorityPendingInteractiveExpirationTime)
) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}

return expirationTime;
}

Expand Down Expand Up @@ -1843,34 +1854,16 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
}
}

function deferredUpdates<A>(fn: () => A): A {
const currentTime = requestCurrentTime();
const previousExpirationContext = expirationContext;
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
expirationContext = computeAsyncExpiration(currentTime);
isBatchingInteractiveUpdates = false;
try {
return fn();
} finally {
expirationContext = previousExpirationContext;
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
}
}

function syncUpdates<A, B, C0, D, R>(
fn: (A, B, C0, D) => R,
a: A,
b: B,
c: C0,
d: D,
): R {
const previousExpirationContext = expirationContext;
expirationContext = Sync;
try {
return runWithPriority(ImmediatePriority, () => {
return fn(a, b, c, d);
} finally {
expirationContext = previousExpirationContext;
}
});
}

// TODO: Everything below this is written as if it has been lifted to the
Expand All @@ -1891,7 +1884,6 @@ let unhandledError: mixed | null = null;

let isBatchingUpdates: boolean = false;
let isUnbatchingUpdates: boolean = false;
let isBatchingInteractiveUpdates: boolean = false;

let completedBatches: Array<Batch> | null = null;

Expand Down Expand Up @@ -2422,7 +2414,9 @@ function completeRoot(
lastCommittedRootDuringThisBatch = root;
nestedUpdateCount = 0;
}
commitRoot(root, finishedWork);
runWithPriority(ImmediatePriority, () => {
commitRoot(root, finishedWork);
});
}

function onUncaughtError(error: mixed) {
Expand Down Expand Up @@ -2488,9 +2482,6 @@ function flushSync<A, R>(fn: (a: A) => R, a: A): R {
}

function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
if (isBatchingInteractiveUpdates) {
return fn(a, b);
}
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
Expand All @@ -2504,14 +2495,13 @@ function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingInteractiveUpdates = true;
isBatchingUpdates = true;
try {
return fn(a, b);
return runWithPriority(UserBlockingPriority, () => {
return fn(a, b);
});
} finally {
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
Expand Down Expand Up @@ -2561,7 +2551,7 @@ export {
unbatchedUpdates,
flushSync,
flushControlled,
deferredUpdates,
Scheduler_next as deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ describe('ReactHooksWithNoopRenderer', () => {
scheduledCallbacks = new Map();
};

let currentPriorityLevel = 3;

return {
unstable_ImmediatePriority: 1,
unstable_UserBlockingPriority: 2,
unstable_NormalPriority: 3,
unstable_LowPriority: 4,
unstable_IdlePriority: 5,

unstable_scheduleCallback(callback) {
const handle = {};
scheduledCallbacks.set(handle, callback);
Expand All @@ -54,6 +62,20 @@ describe('ReactHooksWithNoopRenderer', () => {
unstable_cancelCallback(handle) {
scheduledCallbacks.delete(handle);
},

unstable_runWithPriority(priorityLevel, fn) {
const prevPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return fn();
} finally {
currentPriorityLevel = prevPriorityLevel;
}
},

unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
},
};
});

Expand Down

0 comments on commit 89b06f8

Please sign in to comment.