Skip to content

Commit

Permalink
Scheduler.unstable_next (#14756)
Browse files Browse the repository at this point in the history
* Add Scheduler.unstable_next

* Use Scheduler to prioritize updates

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.

* Add Scheduler.unstable_next

* Use Scheduler to prioritize updates

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 6, 2019
1 parent b5398a9 commit bc9818f
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 78 deletions.
22 changes: 22 additions & 0 deletions packages/react-cache/src/__tests__/ReactCache-test.internal.js
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
148 changes: 70 additions & 78 deletions packages/react-reconciler/src/ReactFiberScheduler.js
Expand Up @@ -15,8 +15,18 @@ 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_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,
hasCaughtError,
Expand Down Expand Up @@ -122,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 @@ -242,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 @@ -793,9 +798,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 = schedulePassiveEffects(callback);
passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
return schedulePassiveEffects(callback);
});
passiveEffectCallback = callback;
}

Expand Down Expand Up @@ -1579,52 +1586,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 @@ -1862,34 +1875,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 @@ -1910,7 +1905,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 @@ -2441,7 +2435,9 @@ function completeRoot(
lastCommittedRootDuringThisBatch = root;
nestedUpdateCount = 0;
}
commitRoot(root, finishedWork);
runWithPriority(ImmediatePriority, () => {
commitRoot(root, finishedWork);
});
}

function onUncaughtError(error: mixed) {
Expand Down Expand Up @@ -2507,9 +2503,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 @@ -2523,14 +2516,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 @@ -2580,7 +2572,7 @@ export {
unbatchedUpdates,
flushSync,
flushControlled,
deferredUpdates,
Scheduler_next as deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/ReactSharedInternals.js
Expand Up @@ -12,6 +12,7 @@ import {
unstable_now,
unstable_scheduleCallback,
unstable_runWithPriority,
unstable_next,
unstable_getFirstCallbackNode,
unstable_pauseExecution,
unstable_continueExecution,
Expand Down Expand Up @@ -53,6 +54,7 @@ if (__UMD__) {
unstable_now,
unstable_scheduleCallback,
unstable_runWithPriority,
unstable_next,
unstable_wrapCallback,
unstable_getFirstCallbackNode,
unstable_pauseExecution,
Expand Down
8 changes: 8 additions & 0 deletions packages/scheduler/npm/umd/scheduler.development.js
Expand Up @@ -54,6 +54,13 @@
);
}

function unstable_next() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
this,
arguments
);
}

function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
Expand Down Expand Up @@ -95,6 +102,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
Expand Down
8 changes: 8 additions & 0 deletions packages/scheduler/npm/umd/scheduler.production.min.js
Expand Up @@ -54,6 +54,13 @@
);
}

function unstable_next() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
this,
arguments
);
}

function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
Expand Down Expand Up @@ -89,6 +96,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
Expand Down
8 changes: 8 additions & 0 deletions packages/scheduler/npm/umd/scheduler.profiling.min.js
Expand Up @@ -54,6 +54,13 @@
);
}

function unstable_next() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
this,
arguments
);
}

function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
Expand Down Expand Up @@ -89,6 +96,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
Expand Down

0 comments on commit bc9818f

Please sign in to comment.