diff --git a/src/renderers/dom/fiber/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js index 18b33854e11a5..4a9d238ec0ec8 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js +++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js @@ -447,11 +447,7 @@ var DOMRenderer = ReactFiberReconciler({ } }, - now() { - // TODO: Use performance.now to enable expiration - // return 0; - return now(); - }, + now, canHydrateInstance( instance: Instance | TextInstance, diff --git a/src/renderers/shared/fiber/ReactFiberExpirationTime.js b/src/renderers/shared/fiber/ReactFiberExpirationTime.js index dd62030d3b380..e05234a384e18 100644 --- a/src/renderers/shared/fiber/ReactFiberExpirationTime.js +++ b/src/renderers/shared/fiber/ReactFiberExpirationTime.js @@ -28,26 +28,40 @@ const invariant = require('fbjs/lib/invariant'); export type ExpirationTime = number; const Done = 0; -exports.Done = Done; +const Sync = 1; +const Task = 2; +const Never = Infinity; -const MAGIC_NUMBER_OFFSET = 2; +const UNIT_SIZE = 10; +const MAGIC_NUMBER_OFFSET = 10; -const Never = Infinity; +exports.Done = Done; exports.Never = Infinity; // 1 unit of expiration time represents 10ms. function msToExpirationTime(ms: number): ExpirationTime { // Always add an offset so that we don't clash with the magic number for Done. - return Math.round(ms / 10) + MAGIC_NUMBER_OFFSET; + return Math.round(ms / UNIT_SIZE) + MAGIC_NUMBER_OFFSET; } exports.msToExpirationTime = msToExpirationTime; function expirationTimeToMs(expirationTime: ExpirationTime): number { - return (expirationTime - MAGIC_NUMBER_OFFSET) * 10; + return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE; } -function ceiling(time: ExpirationTime, precision: number): ExpirationTime { - return Math.ceil(Math.ceil(time * precision) / precision); +function ceiling(num: number, precision: number): number { + return Math.ceil(Math.ceil(num * precision) / precision); +} + +function bucket( + currentTime: ExpirationTime, + expirationInMs: number, + precisionInMs: number, +): ExpirationTime { + return ceiling( + currentTime + expirationInMs / UNIT_SIZE, + precisionInMs / UNIT_SIZE, + ); } // Given the current clock time and a priority level, returns an expiration time @@ -63,21 +77,20 @@ function priorityToExpirationTime( case NoWork: return Done; case SynchronousPriority: - // Return a number lower than the current time, but higher than Done. - return MAGIC_NUMBER_OFFSET - 1; + return Sync; case TaskPriority: - // Return the current time, so that this work completes in this batch. - return currentTime; - case HighPriority: + return Task; + case HighPriority: { // Should complete within ~100ms. 120ms max. - return msToExpirationTime(ceiling(100, 20)); - case LowPriority: + return bucket(currentTime, 100, 20); + } + case LowPriority: { // Should complete within ~1000ms. 1200ms max. - return msToExpirationTime(ceiling(1000, 200)); + return bucket(currentTime, 1000, 200); + } case OffscreenPriority: return Never; default: - console.log(priorityLevel); invariant( false, 'Switch statement should be exhuastive. ' + @@ -95,16 +108,19 @@ function expirationTimeToPriorityLevel( expirationTime: ExpirationTime, ): PriorityLevel { // First check for magic values - if (expirationTime === Done) { - return NoWork; - } - if (expirationTime === Never) { - return OffscreenPriority; - } - if (expirationTime < currentTime) { - return SynchronousPriority; + switch (expirationTime) { + case Done: + return NoWork; + case Sync: + return SynchronousPriority; + case Task: + return TaskPriority; + case Never: + return OffscreenPriority; + default: + break; } - if (expirationTime === currentTime) { + if (expirationTime <= currentTime) { return TaskPriority; } // Keep this value in sync with priorityToExpirationTime. diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 5175b1781b4f8..b7c590afc562c 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -992,7 +992,7 @@ module.exports = function( // Read the current time from the host environment. const currentTime = recalculateCurrentTime(); - const minExpirationTime = priorityToExpirationTime( + const minExpirationTime = getExpirationTimeForPriority( currentTime, minPriorityLevel, ); @@ -1447,30 +1447,36 @@ module.exports = function( const root: FiberRoot = (node.stateNode: any); scheduleRoot(root, expirationTime); if (!isPerformingWork) { - if (expirationTime < mostRecentCurrentTime) { - // This update is synchronous. Perform it now. - if (isUnbatchingUpdates) { - // We're inside unbatchedUpdates, which is inside either - // batchedUpdates or a lifecycle. We should only flush - // synchronous work, not task work. - performWork(SynchronousPriority, null); - } else { - // Flush both synchronous and task work. - performWork(TaskPriority, null); - } - } else if (expirationTime === mostRecentCurrentTime) { - invariant( - isBatchingUpdates, - 'Task updates can only be scheduled as a nested update or ' + - 'inside batchedUpdates. This error is likely caused by a ' + - 'bug in React. Please file an issue.', - ); - } else { - // This update is async. Schedule a callback. - if (!isCallbackScheduled) { - scheduleDeferredCallback(performDeferredWork); - isCallbackScheduled = true; - } + const priorityLevel = expirationTimeToPriorityLevel( + mostRecentCurrentTime, + expirationTime, + ); + switch (priorityLevel) { + case SynchronousPriority: + if (isUnbatchingUpdates) { + // We're inside unbatchedUpdates, which is inside either + // batchedUpdates or a lifecycle. We should only flush + // synchronous work, not task work. + performWork(SynchronousPriority, null); + } else { + // Flush both synchronous and task work. + performWork(TaskPriority, null); + } + break; + case TaskPriority: + invariant( + isBatchingUpdates, + 'Task updates can only be scheduled as a nested update or ' + + 'inside batchedUpdates. This error is likely caused by a ' + + 'bug in React. Please file an issue.', + ); + break; + default: + // This update is async. Schedule a callback. + if (!isCallbackScheduled) { + scheduleDeferredCallback(performDeferredWork); + isCallbackScheduled = true; + } } } } else { @@ -1533,11 +1539,11 @@ module.exports = function( } function scheduleErrorRecovery(fiber: Fiber) { - scheduleUpdateImpl( - fiber, - priorityToExpirationTime(mostRecentCurrentTime, TaskPriority), - true, + const taskTime = getExpirationTimeForPriority( + mostRecentCurrentTime, + TaskPriority, ); + scheduleUpdateImpl(fiber, taskTime, true); } function recalculateCurrentTime(): ExpirationTime {