diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 520283a7c3cc2..7ec53c096a56b 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -28,6 +28,7 @@ import { retryLaneExpirationMs, disableLegacyMode, enableDefaultTransitionIndicator, + enableGestureTransition, } from 'shared/ReactFeatureFlags'; import {isDevToolsPresent} from './ReactFiberDevToolsHook'; import {clz32} from './clz32'; @@ -710,6 +711,9 @@ export function isTransitionLane(lane: Lane): boolean { } export function isGestureRender(lanes: Lanes): boolean { + if (!enableGestureTransition) { + return false; + } // This should render only the one lane. return lanes === GestureLane; } @@ -1271,11 +1275,13 @@ export function getGroupNameOfHighestPriorityLane(lanes: Lanes): string { InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | - DefaultLane | - GestureLane) + DefaultLane) ) { return 'Blocking'; } + if (lanes & GestureLane) { + return 'Gesture'; + } if (lanes & (TransitionHydrationLane | TransitionLanes)) { return 'Transition'; } diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index c19ef704b20b2..dfc6051d957a7 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -33,7 +33,10 @@ import { addObjectDiffToProperties, } from 'shared/ReactPerformanceTrackProperties'; -import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; +import { + enableProfilerTimer, + enableGestureTransition, +} from 'shared/ReactFeatureFlags'; const supportsUserTiming = enableProfilerTimer && @@ -68,6 +71,16 @@ export function markAllLanesInOrder() { LANES_TRACK_GROUP, 'primary-light', ); + if (enableGestureTransition) { + console.timeStamp( + 'Gesture Track', + 0.003, + 0.003, + 'Gesture', + LANES_TRACK_GROUP, + 'primary-light', + ); + } console.timeStamp( 'Transition Track', 0.003, @@ -739,6 +752,145 @@ export function logBlockingStart( } } +export function logGestureStart( + startTime: number, + updateTime: number, + eventTime: number, + eventType: null | string, + eventIsRepeat: boolean, + isPingedUpdate: boolean, + renderStartTime: number, + debugTask: null | ConsoleTask, // DEV-only + updateMethodName: null | string, + updateComponentName: null | string, +): void { + if (supportsUserTiming) { + currentTrack = 'Gesture'; + // Clamp start times + if (updateTime > 0) { + if (updateTime > renderStartTime) { + updateTime = renderStartTime; + } + } else { + updateTime = renderStartTime; + } + if (startTime > 0) { + if (startTime > updateTime) { + startTime = updateTime; + } + } else { + startTime = updateTime; + } + if (eventTime > 0) { + if (eventTime > startTime) { + eventTime = startTime; + } + } else { + eventTime = startTime; + } + + if (startTime > eventTime && eventType !== null) { + // Log the time from the event timeStamp until we started a gesture. + const color = eventIsRepeat ? 'secondary-light' : 'warning'; + if (__DEV__ && debugTask) { + debugTask.run( + console.timeStamp.bind( + console, + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, + eventTime, + startTime, + currentTrack, + LANES_TRACK_GROUP, + color, + ), + ); + } else { + console.timeStamp( + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, + eventTime, + startTime, + currentTrack, + LANES_TRACK_GROUP, + color, + ); + } + } + if (updateTime > startTime) { + // Log the time from when we started a gesture until we called setState or started rendering. + if (__DEV__ && debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + console.timeStamp.bind( + console, + 'Gesture', + startTime, + updateTime, + currentTrack, + LANES_TRACK_GROUP, + 'primary-dark', + ), + ); + } else { + console.timeStamp( + 'Gesture', + startTime, + updateTime, + currentTrack, + LANES_TRACK_GROUP, + 'primary-dark', + ); + } + } + if (renderStartTime > updateTime) { + // Log the time from when we called setState until we started rendering. + const label = isPingedUpdate + ? 'Promise Resolved' + : renderStartTime - updateTime > 5 + ? 'Update Blocked' + : 'Update'; + if (__DEV__) { + const properties = []; + if (updateComponentName != null) { + properties.push(['Component name', updateComponentName]); + } + if (updateMethodName != null) { + properties.push(['Method name', updateMethodName]); + } + const measureOptions = { + start: updateTime, + end: renderStartTime, + detail: { + devtools: { + properties, + track: currentTrack, + trackGroup: LANES_TRACK_GROUP, + color: 'primary-light', + }, + }, + }; + + if (debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + performance.measure.bind(performance, label, measureOptions), + ); + } else { + performance.measure(label, measureOptions); + } + } else { + console.timeStamp( + label, + updateTime, + renderStartTime, + currentTrack, + LANES_TRACK_GROUP, + 'primary-light', + ); + } + } + } +} + export function logTransitionStart( startTime: number, updateTime: number, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 4e67f62905827..331708fb195d4 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -71,6 +71,7 @@ import { } from './Scheduler'; import { logBlockingStart, + logGestureStart, logTransitionStart, logRenderPhase, logInterruptedRenderPhase, @@ -282,6 +283,17 @@ import { blockingEventType, blockingEventIsRepeat, blockingSuspendedTime, + gestureClampTime, + gestureStartTime, + gestureUpdateTime, + gestureUpdateTask, + gestureUpdateType, + gestureUpdateMethodName, + gestureUpdateComponentName, + gestureEventTime, + gestureEventType, + gestureEventIsRepeat, + gestureSuspendedTime, transitionClampTime, transitionStartTime, transitionUpdateTime, @@ -294,8 +306,10 @@ import { transitionEventIsRepeat, transitionSuspendedTime, clearBlockingTimers, + clearGestureTimers, clearTransitionTimers, clampBlockingTimers, + clampGestureTimers, clampTransitionTimers, clampRetryTimers, clampIdleTimers, @@ -1898,7 +1912,9 @@ function resetWorkInProgressStack() { function finalizeRender(lanes: Lanes, finalizationTime: number): void { if (enableProfilerTimer && enableComponentPerformanceTrack) { - if (includesBlockingLane(lanes)) { + if (isGestureRender(lanes)) { + clampGestureTimers(finalizationTime); + } else if (includesBlockingLane(lanes)) { clampBlockingTimers(finalizationTime); } if (includesTransitionLane(lanes)) { @@ -1963,7 +1979,58 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { const previousUpdateTask = workInProgressUpdateTask; workInProgressUpdateTask = null; - if (includesBlockingLane(lanes)) { + if (isGestureRender(lanes)) { + workInProgressUpdateTask = gestureUpdateTask; + const clampedStartTime = + gestureStartTime >= 0 && gestureStartTime < gestureClampTime + ? gestureClampTime + : gestureStartTime; + const clampedUpdateTime = + gestureUpdateTime >= 0 && gestureUpdateTime < gestureClampTime + ? gestureClampTime + : gestureUpdateTime; + const clampedEventTime = + gestureEventTime >= 0 && gestureEventTime < gestureClampTime + ? gestureClampTime + : gestureEventTime; + const clampedRenderStartTime = + // Clamp the suspended time to the first event/update. + clampedEventTime >= 0 + ? clampedEventTime + : clampedUpdateTime >= 0 + ? clampedUpdateTime + : renderStartTime; + if (gestureSuspendedTime >= 0) { + setCurrentTrackFromLanes(GestureLane); + logSuspendedWithDelayPhase( + gestureSuspendedTime, + clampedRenderStartTime, + lanes, + workInProgressUpdateTask, + ); + } else if (isGestureRender(animatingLanes)) { + // If this lane is still animating, log the time from previous render finishing to now as animating. + setCurrentTrackFromLanes(GestureLane); + logAnimatingPhase( + gestureClampTime, + clampedRenderStartTime, + animatingTask, + ); + } + logGestureStart( + clampedStartTime, + clampedUpdateTime, + clampedEventTime, + gestureEventType, + gestureEventIsRepeat, + gestureUpdateType === PINGED_UPDATE, + renderStartTime, + gestureUpdateTask, + gestureUpdateMethodName, + gestureUpdateComponentName, + ); + clearGestureTimers(); + } else if (includesBlockingLane(lanes)) { workInProgressUpdateTask = blockingUpdateTask; const clampedUpdateTime = blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime @@ -3716,12 +3783,12 @@ function finishedViewTransition(lanes: Lanes): void { // If an affected track isn't in the middle of rendering or committing, log from the previous // finished render until the end of the animation. if ( - includesBlockingLane(lanes) && - !includesBlockingLane(workInProgressRootRenderLanes) && - !includesBlockingLane(pendingEffectsLanes) + isGestureRender(lanes) && + !isGestureRender(workInProgressRootRenderLanes) && + !isGestureRender(pendingEffectsLanes) ) { - setCurrentTrackFromLanes(SyncLane); - logAnimatingPhase(blockingClampTime, now(), task); + setCurrentTrackFromLanes(GestureLane); + logAnimatingPhase(gestureClampTime, now(), task); } if ( includesTransitionLane(lanes) && diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 8b7e4274e16c7..acaf540c6a6ca 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -18,6 +18,7 @@ import type {CapturedValue} from './ReactCapturedValue'; import { isTransitionLane, isBlockingLane, + isGestureRender, includesTransitionLane, includesBlockingLane, NoLanes, @@ -74,6 +75,19 @@ export let blockingEventTime: number = -1.1; // Event timeStamp of the first set export let blockingEventType: null | string = null; // Event type of the first setState. export let blockingEventIsRepeat: boolean = false; export let blockingSuspendedTime: number = -1.1; + +export let gestureClampTime: number = -0; +export let gestureStartTime: number = -1.1; // First startGestureTransition call before setOptimistic. +export let gestureUpdateTime: number = -1.1; // First setOptimistic scheduled inside startGestureTransition. +export let gestureUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace. +export let gestureUpdateType: UpdateType = 0; +export let gestureUpdateMethodName: null | string = null; // The name of the method that caused first gesture update. +export let gestureUpdateComponentName: null | string = null; // The name of the component where first gesture update happened. +export let gestureEventTime: number = -1.1; // Event timeStamp of the first setState. +export let gestureEventType: null | string = null; // Event type of the first setState. +export let gestureEventIsRepeat: boolean = false; +export let gestureSuspendedTime: number = -1.1; + // TODO: This should really be one per Transition lane. export let transitionClampTime: number = -0; export let transitionStartTime: number = -1.1; // First startTransition call before setState. @@ -112,7 +126,28 @@ export function startUpdateTimerByLane( if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; } - if (isBlockingLane(lane)) { + if (isGestureRender(lane)) { + if (gestureUpdateTime < 0) { + gestureUpdateTime = now(); + gestureUpdateTask = createTask(method); + gestureUpdateMethodName = method; + if (__DEV__ && fiber != null) { + gestureUpdateComponentName = getComponentNameFromFiber(fiber); + } + if (gestureStartTime < 0) { + const newEventTime = resolveEventTimeStamp(); + const newEventType = resolveEventType(); + if ( + newEventTime !== gestureEventTime || + newEventType !== gestureEventType + ) { + gestureEventIsRepeat = false; + } + gestureEventTime = newEventTime; + gestureEventType = newEventType; + } + } + } else if (isBlockingLane(lane)) { if (blockingUpdateTime < 0) { blockingUpdateTime = now(); blockingUpdateTask = createTask(method); @@ -218,7 +253,13 @@ export function startPingTimerByLanes(lanes: Lanes): void { // Mark the update time and clamp anything before it because we don't want // to show the event time for pings but we also don't want to clear it // because we still need to track if this was a repeat. - if (includesBlockingLane(lanes)) { + if (isGestureRender(lanes)) { + if (gestureUpdateTime < 0) { + gestureClampTime = gestureUpdateTime = now(); + gestureUpdateTask = createTask('Promise Resolved'); + gestureUpdateType = PINGED_UPDATE; + } + } else if (includesBlockingLane(lanes)) { if (blockingUpdateTime < 0) { blockingClampTime = blockingUpdateTime = now(); blockingUpdateTask = createTask('Promise Resolved'); @@ -237,7 +278,9 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; } - if (includesBlockingLane(lanes)) { + if (isGestureRender(lanes)) { + gestureSuspendedTime = renderEndTime; + } else if (includesBlockingLane(lanes)) { blockingSuspendedTime = renderEndTime; } else if (includesTransitionLane(lanes)) { transitionSuspendedTime = renderEndTime; @@ -291,6 +334,43 @@ export function clearTransitionTimers(): void { transitionClampTime = now(); } +export function startGestureTransitionTimer(): void { + if (!enableProfilerTimer || !enableComponentPerformanceTrack) { + return; + } + if (gestureStartTime < 0 && gestureUpdateTime < 0) { + gestureStartTime = now(); + const newEventTime = resolveEventTimeStamp(); + const newEventType = resolveEventType(); + if ( + newEventTime !== gestureEventTime || + newEventType !== gestureEventType + ) { + gestureEventIsRepeat = false; + } + gestureEventTime = newEventTime; + gestureEventType = newEventType; + } +} + +export function hasScheduledGestureTransitionWork(): boolean { + // If we have call setOptimistic on a gesture + return gestureUpdateTime > -1; +} + +export function clearGestureTransitionTimer(): void { + gestureStartTime = -1.1; +} + +export function clearGestureTimers(): void { + gestureStartTime = -1.1; + gestureUpdateTime = -1.1; + gestureUpdateType = 0; + gestureSuspendedTime = -1.1; + gestureEventIsRepeat = true; + gestureClampTime = now(); +} + export function clampBlockingTimers(finalTime: number): void { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; @@ -301,6 +381,16 @@ export function clampBlockingTimers(finalTime: number): void { blockingClampTime = finalTime; } +export function clampGestureTimers(finalTime: number): void { + if (!enableProfilerTimer || !enableComponentPerformanceTrack) { + return; + } + // If we had new updates come in while we were still rendering or committing, we don't want + // those update times to create overlapping tracks in the performance timeline so we clamp + // them to the end of the commit phase. + gestureClampTime = finalTime; +} + export function clampTransitionTimers(finalTime: number): void { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return;