Skip to content

Commit bc9818f

Browse files
authored
Scheduler.unstable_next (#14756)
* 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.
1 parent b5398a9 commit bc9818f

File tree

8 files changed

+166
-78
lines changed

8 files changed

+166
-78
lines changed

Diff for: packages/react-cache/src/__tests__/ReactCache-test.internal.js

+22
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ describe('ReactCache', () => {
2424
beforeEach(() => {
2525
jest.resetModules();
2626

27+
let currentPriorityLevel = 3;
28+
2729
jest.mock('scheduler', () => {
2830
let callbacks = [];
2931
return {
@@ -38,6 +40,26 @@ describe('ReactCache', () => {
3840
callback();
3941
}
4042
},
43+
44+
unstable_ImmediatePriority: 1,
45+
unstable_UserBlockingPriority: 2,
46+
unstable_NormalPriority: 3,
47+
unstable_LowPriority: 4,
48+
unstable_IdlePriority: 5,
49+
50+
unstable_runWithPriority(priorityLevel, fn) {
51+
const prevPriorityLevel = currentPriorityLevel;
52+
currentPriorityLevel = priorityLevel;
53+
try {
54+
return fn();
55+
} finally {
56+
currentPriorityLevel = prevPriorityLevel;
57+
}
58+
},
59+
60+
unstable_getCurrentPriorityLevel() {
61+
return currentPriorityLevel;
62+
},
4163
};
4264
});
4365

Diff for: packages/react-reconciler/src/ReactFiberScheduler.js

+70-78
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ import type {Interaction} from 'scheduler/src/Tracing';
1515
import {
1616
__interactionsRef,
1717
__subscriberRef,
18-
unstable_wrap as Schedule_tracing_wrap,
18+
unstable_wrap as Scheduler_tracing_wrap,
1919
} from 'scheduler/tracing';
20+
import {
21+
unstable_next as Scheduler_next,
22+
unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
23+
unstable_runWithPriority as runWithPriority,
24+
unstable_ImmediatePriority as ImmediatePriority,
25+
unstable_UserBlockingPriority as UserBlockingPriority,
26+
unstable_NormalPriority as NormalPriority,
27+
unstable_LowPriority as LowPriority,
28+
unstable_IdlePriority as IdlePriority,
29+
} from 'scheduler';
2030
import {
2131
invokeGuardedCallback,
2232
hasCaughtError,
@@ -122,7 +132,7 @@ import {
122132
computeAsyncExpiration,
123133
computeInteractiveExpiration,
124134
} from './ReactFiberExpirationTime';
125-
import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
135+
import {ConcurrentMode, ProfileMode, NoContext} from './ReactTypeOfMode';
126136
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
127137
import {createCapturedValue} from './ReactCapturedValue';
128138
import {
@@ -242,11 +252,6 @@ if (__DEV__) {
242252
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
243253
let lastUniqueAsyncExpiration: number = Sync - 1;
244254

245-
// Represents the expiration time that incoming updates should use. (If this
246-
// is NoWork, use the default strategy: async updates in async mode, sync
247-
// updates in sync mode.)
248-
let expirationContext: ExpirationTime = NoWork;
249-
250255
let isWorking: boolean = false;
251256

252257
// The next work in progress fiber that we're currently working on.
@@ -793,9 +798,11 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
793798
// TODO: Avoid this extra callback by mutating the tracing ref directly,
794799
// like we do at the beginning of commitRoot. I've opted not to do that
795800
// here because that code is still in flux.
796-
callback = Schedule_tracing_wrap(callback);
801+
callback = Scheduler_tracing_wrap(callback);
797802
}
798-
passiveEffectCallbackHandle = schedulePassiveEffects(callback);
803+
passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
804+
return schedulePassiveEffects(callback);
805+
});
799806
passiveEffectCallback = callback;
800807
}
801808

@@ -1579,52 +1586,58 @@ function computeUniqueAsyncExpiration(): ExpirationTime {
15791586
}
15801587

15811588
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
1589+
const priorityLevel = getCurrentPriorityLevel();
1590+
15821591
let expirationTime;
1583-
if (expirationContext !== NoWork) {
1584-
// An explicit expiration context was set;
1585-
expirationTime = expirationContext;
1586-
} else if (isWorking) {
1587-
if (isCommitting) {
1588-
// Updates that occur during the commit phase should have sync priority
1589-
// by default.
1590-
expirationTime = Sync;
1591-
} else {
1592-
// Updates during the render phase should expire at the same time as
1593-
// the work that is being rendered.
1594-
expirationTime = nextRenderExpirationTime;
1595-
}
1592+
if ((fiber.mode & ConcurrentMode) === NoContext) {
1593+
// Outside of concurrent mode, updates are always synchronous.
1594+
expirationTime = Sync;
1595+
} else if (isWorking && !isCommitting) {
1596+
// During render phase, updates expire during as the current render.
1597+
expirationTime = nextRenderExpirationTime;
15961598
} else {
1597-
// No explicit expiration context was set, and we're not currently
1598-
// performing work. Calculate a new expiration time.
1599-
if (fiber.mode & ConcurrentMode) {
1600-
if (isBatchingInteractiveUpdates) {
1601-
// This is an interactive update
1599+
switch (priorityLevel) {
1600+
case ImmediatePriority:
1601+
expirationTime = Sync;
1602+
break;
1603+
case UserBlockingPriority:
16021604
expirationTime = computeInteractiveExpiration(currentTime);
1603-
} else {
1604-
// This is an async update
1605+
break;
1606+
case NormalPriority:
1607+
// This is a normal, concurrent update
16051608
expirationTime = computeAsyncExpiration(currentTime);
1606-
}
1607-
// If we're in the middle of rendering a tree, do not update at the same
1608-
// expiration time that is already rendering.
1609-
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
1610-
expirationTime -= 1;
1611-
}
1612-
} else {
1613-
// This is a sync update
1614-
expirationTime = Sync;
1609+
break;
1610+
case LowPriority:
1611+
case IdlePriority:
1612+
expirationTime = Never;
1613+
break;
1614+
default:
1615+
invariant(
1616+
false,
1617+
'Unknown priority level. This error is likely caused by a bug in ' +
1618+
'React. Please file an issue.',
1619+
);
16151620
}
1616-
}
1617-
if (isBatchingInteractiveUpdates) {
1618-
// This is an interactive update. Keep track of the lowest pending
1619-
// interactive expiration time. This allows us to synchronously flush
1620-
// all interactive updates when needed.
1621-
if (
1622-
lowestPriorityPendingInteractiveExpirationTime === NoWork ||
1623-
expirationTime < lowestPriorityPendingInteractiveExpirationTime
1624-
) {
1625-
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
1621+
1622+
// If we're in the middle of rendering a tree, do not update at the same
1623+
// expiration time that is already rendering.
1624+
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
1625+
expirationTime -= 1;
16261626
}
16271627
}
1628+
1629+
// Keep track of the lowest pending interactive expiration time. This
1630+
// allows us to synchronously flush all interactive updates
1631+
// when needed.
1632+
// TODO: Move this to renderer?
1633+
if (
1634+
priorityLevel === UserBlockingPriority &&
1635+
(lowestPriorityPendingInteractiveExpirationTime === NoWork ||
1636+
expirationTime < lowestPriorityPendingInteractiveExpirationTime)
1637+
) {
1638+
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
1639+
}
1640+
16281641
return expirationTime;
16291642
}
16301643

@@ -1862,34 +1875,16 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
18621875
}
18631876
}
18641877

1865-
function deferredUpdates<A>(fn: () => A): A {
1866-
const currentTime = requestCurrentTime();
1867-
const previousExpirationContext = expirationContext;
1868-
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1869-
expirationContext = computeAsyncExpiration(currentTime);
1870-
isBatchingInteractiveUpdates = false;
1871-
try {
1872-
return fn();
1873-
} finally {
1874-
expirationContext = previousExpirationContext;
1875-
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1876-
}
1877-
}
1878-
18791878
function syncUpdates<A, B, C0, D, R>(
18801879
fn: (A, B, C0, D) => R,
18811880
a: A,
18821881
b: B,
18831882
c: C0,
18841883
d: D,
18851884
): R {
1886-
const previousExpirationContext = expirationContext;
1887-
expirationContext = Sync;
1888-
try {
1885+
return runWithPriority(ImmediatePriority, () => {
18891886
return fn(a, b, c, d);
1890-
} finally {
1891-
expirationContext = previousExpirationContext;
1892-
}
1887+
});
18931888
}
18941889

18951890
// TODO: Everything below this is written as if it has been lifted to the
@@ -1910,7 +1905,6 @@ let unhandledError: mixed | null = null;
19101905

19111906
let isBatchingUpdates: boolean = false;
19121907
let isUnbatchingUpdates: boolean = false;
1913-
let isBatchingInteractiveUpdates: boolean = false;
19141908

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

@@ -2441,7 +2435,9 @@ function completeRoot(
24412435
lastCommittedRootDuringThisBatch = root;
24422436
nestedUpdateCount = 0;
24432437
}
2444-
commitRoot(root, finishedWork);
2438+
runWithPriority(ImmediatePriority, () => {
2439+
commitRoot(root, finishedWork);
2440+
});
24452441
}
24462442

24472443
function onUncaughtError(error: mixed) {
@@ -2507,9 +2503,6 @@ function flushSync<A, R>(fn: (a: A) => R, a: A): R {
25072503
}
25082504

25092505
function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
2510-
if (isBatchingInteractiveUpdates) {
2511-
return fn(a, b);
2512-
}
25132506
// If there are any pending interactive updates, synchronously flush them.
25142507
// This needs to happen before we read any handlers, because the effect of
25152508
// the previous event may influence which handlers are called during
@@ -2523,14 +2516,13 @@ function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
25232516
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
25242517
lowestPriorityPendingInteractiveExpirationTime = NoWork;
25252518
}
2526-
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
25272519
const previousIsBatchingUpdates = isBatchingUpdates;
2528-
isBatchingInteractiveUpdates = true;
25292520
isBatchingUpdates = true;
25302521
try {
2531-
return fn(a, b);
2522+
return runWithPriority(UserBlockingPriority, () => {
2523+
return fn(a, b);
2524+
});
25322525
} finally {
2533-
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
25342526
isBatchingUpdates = previousIsBatchingUpdates;
25352527
if (!isBatchingUpdates && !isRendering) {
25362528
performSyncWork();
@@ -2580,7 +2572,7 @@ export {
25802572
unbatchedUpdates,
25812573
flushSync,
25822574
flushControlled,
2583-
deferredUpdates,
2575+
Scheduler_next as deferredUpdates,
25842576
syncUpdates,
25852577
interactiveUpdates,
25862578
flushInteractiveUpdates,

Diff for: packages/react/src/ReactSharedInternals.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
unstable_now,
1313
unstable_scheduleCallback,
1414
unstable_runWithPriority,
15+
unstable_next,
1516
unstable_getFirstCallbackNode,
1617
unstable_pauseExecution,
1718
unstable_continueExecution,
@@ -53,6 +54,7 @@ if (__UMD__) {
5354
unstable_now,
5455
unstable_scheduleCallback,
5556
unstable_runWithPriority,
57+
unstable_next,
5658
unstable_wrapCallback,
5759
unstable_getFirstCallbackNode,
5860
unstable_pauseExecution,

Diff for: packages/scheduler/npm/umd/scheduler.development.js

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
);
5555
}
5656

57+
function unstable_next() {
58+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59+
this,
60+
arguments
61+
);
62+
}
63+
5764
function unstable_wrapCallback() {
5865
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
5966
this,
@@ -95,6 +102,7 @@
95102
unstable_cancelCallback: unstable_cancelCallback,
96103
unstable_shouldYield: unstable_shouldYield,
97104
unstable_runWithPriority: unstable_runWithPriority,
105+
unstable_next: unstable_next,
98106
unstable_wrapCallback: unstable_wrapCallback,
99107
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
100108
unstable_continueExecution: unstable_continueExecution,

Diff for: packages/scheduler/npm/umd/scheduler.production.min.js

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
);
5555
}
5656

57+
function unstable_next() {
58+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59+
this,
60+
arguments
61+
);
62+
}
63+
5764
function unstable_wrapCallback() {
5865
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
5966
this,
@@ -89,6 +96,7 @@
8996
unstable_cancelCallback: unstable_cancelCallback,
9097
unstable_shouldYield: unstable_shouldYield,
9198
unstable_runWithPriority: unstable_runWithPriority,
99+
unstable_next: unstable_next,
92100
unstable_wrapCallback: unstable_wrapCallback,
93101
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94102
unstable_continueExecution: unstable_continueExecution,

Diff for: packages/scheduler/npm/umd/scheduler.profiling.min.js

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
);
5555
}
5656

57+
function unstable_next() {
58+
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59+
this,
60+
arguments
61+
);
62+
}
63+
5764
function unstable_wrapCallback() {
5865
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
5966
this,
@@ -89,6 +96,7 @@
8996
unstable_cancelCallback: unstable_cancelCallback,
9097
unstable_shouldYield: unstable_shouldYield,
9198
unstable_runWithPriority: unstable_runWithPriority,
99+
unstable_next: unstable_next,
92100
unstable_wrapCallback: unstable_wrapCallback,
93101
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94102
unstable_continueExecution: unstable_continueExecution,

0 commit comments

Comments
 (0)