208
};
209
}
210
211
-// Represents the current time in ms.
212
-const originalStartTimeMs = now();
213
-let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
214
-let mostRecentCurrentTimeMs: number = originalStartTimeMs;
215
-
216
-// Used to ensure computeUniqueAsyncExpiration is monotonically increases.
211
+// Used to ensure computeUniqueAsyncExpiration is monotonically increasing.
212
let lastUniqueAsyncExpiration: number = 0;
213
214
// Represents the expiration time that incoming updates should use. (If this
427
428
function commitAllLifeCycles(
429
finishedRoot: FiberRoot,
435
- currentTime: ExpirationTime,
430
committedExpirationTime: ExpirationTime,
431
) {
432
if (__DEV__) {
450
finishedRoot,
451
current,
452
nextEffect,
459
- currentTime,
453
committedExpirationTime,
454
);
455
}
509
// about to commit. This needs to happen before calling the lifecycles, since
510
// they may schedule additional updates.
511
const earliestRemainingTime = finishedWork.expirationTime;
519
- const currentTime = recalculateCurrentTime();
520
- markCommittedPriorityLevels(root, currentTime, earliestRemainingTime);
512
+ markCommittedPriorityLevels(root, earliestRemainingTime);
513
514
// Reset this to null before calling lifecycles
515
ReactCurrentOwner.current = null;
634
commitAllLifeCycles,
635
null,
636
root,
645
- currentTime,
637
committedExpirationTime,
638
);
639
if (hasCaughtError()) {
642
}
643
} else {
644
try {
654
- commitAllLifeCycles(root, currentTime, committedExpirationTime);
645
+ commitAllLifeCycles(root, committedExpirationTime);
646
} catch (e) {
647
didError = true;
648
error = e;
1067
sourceFiber,
1068
thrownValue,
1069
nextRenderExpirationTime,
1079
- mostRecentCurrentTimeMs,
1070
);
1071
nextUnitOfWork = completeUnitOfWork(sourceFiber);
1072
}
1182
return dispatch(fiber, error, Sync);
1183
}
1184
1195
-function computeAsyncExpiration(currentTime: ExpirationTime) {
1185
+function computeAsyncExpiration(currentTime) {
1186
// Given the current clock time, returns an expiration time. We use rounding
1187
// to batch like updates together.
1188
// Should complete within ~5000ms. 5250ms max.
1191
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1192
}
1193
1204
-function computeInteractiveExpiration(currentTime: ExpirationTime) {
1194
+function computeInteractiveExpiration(currentTime) {
1195
let expirationMs;
1196
// We intentionally set a higher expiration time for interactive updates in
1197
// dev than in production.
1214
1215
// Creates a unique async expiration time.
1216
function computeUniqueAsyncExpiration(): ExpirationTime {
1227
- const currentTime = recalculateCurrentTime();
1217
+ const currentTime = requestCurrentTime();
1218
let result = computeAsyncExpiration(currentTime);
1219
if (result <= lastUniqueAsyncExpiration) {
1220
// Since we assume the current time monotonically increases, we only hit
1369
}
1370
}
1371
1382
-function recalculateCurrentTime(): ExpirationTime {
1383
- // Subtract initial time so it fits inside 32bits
1384
- mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1385
- mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1386
- return mostRecentCurrentTime;
1387
-}
1388
-
1372
function deferredUpdates<A>(fn: () => A): A {
1373
+ const currentTime = requestCurrentTime();
1374
const previousExpirationContext = expirationContext;
1391
- const currentTime = recalculateCurrentTime();
1375
expirationContext = computeAsyncExpiration(currentTime);
1376
try {
1377
return fn();
1379
expirationContext = previousExpirationContext;
1380
}
1381
}
1382
+
1383
function syncUpdates<A, B, C0, D, R>(
1384
fn: (A, B, C0, D) => R,
1385
a: A,
1420
1421
let completedBatches: Array<Batch> | null = null;
1422
1423
+let originalStartTimeMs: number = now();
1424
+let currentRendererTime: ExpirationTime = msToExpirationTime(
1425
+ originalStartTimeMs,
1426
+);
1427
+let currentSchedulerTime: ExpirationTime = currentRendererTime;
1428
+
1429
// Use these to prevent an infinite loop of nested updates
1430
const NESTED_UPDATE_LIMIT = 1000;
1431
let nestedUpdateCount: number = 0;
1432
1433
const timeHeuristicForUnitOfWork = 1;
1434
1435
+function recomputeCurrentRendererTime() {
1436
+ const currentTimeMs = now() - originalStartTimeMs;
1437
+ currentRendererTime = msToExpirationTime(currentTimeMs);
1438
+}
1439
+
1440
function scheduleCallbackWithExpirationTime(expirationTime) {
1441
if (callbackExpirationTime !== NoWork) {
1442
// A callback is already scheduled. Check its expiration time (timeout).
1503
root.finishedWork = null;
1504
}
1505
1506
+function requestCurrentTime() {
1507
+ // requestCurrentTime is called by the scheduler to compute an expiration
1508
+ // time.
1509
+ //
1510
+ // Expiration times are computed by adding to the current time (the start
1511
+ // time). However, if two updates are scheduled within the same event, we
1512
+ // should treat their start times as simultaneous, even if the actual clock
1513
+ // time has advanced between the first and second call.
1514
+
1515
+ // In other words, because expiration times determine how updates are batched,
1516
+ // we want all updates of like priority that occur within the same event to
1517
+ // receive the same expiration time. Otherwise we get tearing.
1518
+ //
1519
+ // We keep track of two separate times: the current "renderer" time and the
1520
+ // current "scheduler" time. The renderer time can be updated whenever; it
1521
+ // only exists to minimize the calls performance.now.
1522
+ //
1523
+ // But the scheduler time can only be updated if there's no pending work, or
1524
+ // if we know for certain that we're not in the middle of an event.
1525
+
1526
+ if (isRendering) {
1527
+ // We're already rendering. Return the most recently read time.
1528
+ return currentSchedulerTime;
1529
+ }
1530
+ // Check if there's pending work.
1531
+ findHighestPriorityRoot();
1532
+ if (
1533
+ nextFlushedExpirationTime === NoWork ||
1534
+ nextFlushedExpirationTime === Never
1535
+ ) {
1536
+ // If there's no pending work, or if the pending work is offscreen, we can
1537
+ // read the current time without risk of tearing.
1538
+ recomputeCurrentRendererTime();
1539
+ currentSchedulerTime = currentRendererTime;
1540
+ return currentSchedulerTime;
1541
+ }
1542
+ // There's already pending work. We might be in the middle of a browser
1543
+ // event. If we were to read the current time, it could cause multiple updates
1544
+ // within the same event to receive different expiration times, leading to
1545
+ // tearing. Return the last read time. During the next idle callback, the
1546
+ // time will be updated.
1547
+ return currentSchedulerTime;
1548
+}
1549
+
1550
// requestWork is called by the scheduler whenever a root receives an update.
1551
// It's up to the renderer to call renderRoot at some point in the future.
1552
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1700
resumeActualRenderTimerIfPaused();
1701
}
1702
1664
- if (enableUserTimingAPI && deadline !== null) {
1665
- const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
1666
- const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1667
- stopRequestCallbackTimer(didExpire, timeout);
1668
- }
1669
-
1703
if (deadline !== null) {
1704
+ recomputeCurrentRendererTime();
1705
+ currentSchedulerTime = currentRendererTime;
1706
+
1707
+ if (enableUserTimingAPI) {
1708
+ const didExpire = nextFlushedExpirationTime < currentRendererTime;
1709
+ const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1710
+ stopRequestCallbackTimer(didExpire, timeout);
1711
+ }
1712
+
1713
while (
1714
nextFlushedRoot !== null &&
1715
nextFlushedExpirationTime !== NoWork &&
1716
(minExpirationTime === NoWork ||
1717
minExpirationTime >= nextFlushedExpirationTime) &&
1676
- (!deadlineDidExpire ||
1677
- recalculateCurrentTime() >= nextFlushedExpirationTime)
1718
+ (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
1719
) {
1679
- recalculateCurrentTime();
1720
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
1721
findHighestPriorityRoot();
1722
+ recomputeCurrentRendererTime();
1723
+ currentSchedulerTime = currentRendererTime;
1724
}
1725
} else {
1726
while (
2019
}
2020
2021
export {
1980
- recalculateCurrentTime,
2022
+ requestCurrentTime,
2023
computeExpirationForFiber,
2024
captureCommitPhaseError,
2025
onUncaughtError,