mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Optimize gesture by allowing the original work in progress tree to be a suspended commit (#35510)
Stacked on #35487. This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first. If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)
This commit is contained in:
committed by
GitHub
parent
c0d3400cb1
commit
3c142f4419
@@ -114,16 +114,17 @@ export default function SwipeRecognizer({
|
||||
);
|
||||
}
|
||||
function onGestureEnd(changed) {
|
||||
// Reset scroll
|
||||
if (changed) {
|
||||
// Trigger side-effects
|
||||
startTransition(action);
|
||||
}
|
||||
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
|
||||
// before scheduling new updates.
|
||||
if (activeGesture.current !== null) {
|
||||
const cancelGesture = activeGesture.current;
|
||||
activeGesture.current = null;
|
||||
cancelGesture();
|
||||
}
|
||||
if (changed) {
|
||||
// Trigger side-effects
|
||||
startTransition(action);
|
||||
}
|
||||
}
|
||||
function onScrollEnd() {
|
||||
if (touchTimeline.current) {
|
||||
|
||||
+23
-3
@@ -35,6 +35,7 @@ export type ScheduledGesture = {
|
||||
rangeEnd: number, // The percentage along the timeline where the "destination" state is reached.
|
||||
types: null | TransitionTypes, // Any addTransitionType call made during startGestureTransition.
|
||||
running: null | RunningViewTransition, // Used to cancel the running transition after we're done.
|
||||
commit: null | (() => void), // Callback to run to commit if there's a pending commit.
|
||||
committing: boolean, // If the gesture was released in a committed state and should actually commit.
|
||||
revertLane: Lane, // The Lane that we'll use to schedule the revert.
|
||||
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
|
||||
@@ -64,6 +65,7 @@ export function scheduleGesture(
|
||||
rangeEnd: 100, // Uninitialized
|
||||
types: null,
|
||||
running: null,
|
||||
commit: null,
|
||||
committing: false,
|
||||
revertLane: NoLane, // Starts uninitialized.
|
||||
prev: prev,
|
||||
@@ -164,9 +166,17 @@ export function cancelScheduledGesture(
|
||||
// lane to actually commit it.
|
||||
gesture.committing = true;
|
||||
if (root.pendingGestures === gesture) {
|
||||
// Ping the root given the new state. This is similar to pingSuspendedRoot.
|
||||
// This will either schedule the gesture lane to be committed possibly from its current state.
|
||||
pingGestureRoot(root);
|
||||
const commitCallback = gesture.commit;
|
||||
if (commitCallback !== null) {
|
||||
gesture.commit = null;
|
||||
// If we already have a commit prepared we can immediately commit the tree
|
||||
// without rerendering.
|
||||
// TODO: Consider scheduling this in a task instead of synchronously inside the last cancellation.s
|
||||
commitCallback();
|
||||
} else {
|
||||
// Ping the root given the new state. This is similar to pingSuspendedRoot.
|
||||
pingGestureRoot(root);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we're not going to commit this gesture we can stop the View Transition
|
||||
@@ -235,3 +245,13 @@ export function stopCommittedGesture(root: FiberRoot) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function scheduleGestureCommit(
|
||||
gesture: ScheduledGesture,
|
||||
callback: () => void,
|
||||
): () => void {
|
||||
gesture.commit = callback;
|
||||
return function () {
|
||||
gesture.commit = null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1569,6 +1569,41 @@ export function logPaintYieldPhase(
|
||||
}
|
||||
}
|
||||
|
||||
export function logApplyGesturePhase(
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
debugTask: null | ConsoleTask,
|
||||
): void {
|
||||
if (supportsUserTiming) {
|
||||
if (endTime <= startTime) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__ && debugTask) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
'Create Ghost Tree',
|
||||
startTime,
|
||||
endTime,
|
||||
currentTrack,
|
||||
LANES_TRACK_GROUP,
|
||||
'secondary-dark',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
'Create Ghost Tree',
|
||||
startTime,
|
||||
endTime,
|
||||
currentTrack,
|
||||
LANES_TRACK_GROUP,
|
||||
'secondary-dark',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logStartViewTransitionYieldPhase(
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
|
||||
+61
-19
@@ -92,6 +92,7 @@ import {
|
||||
logSuspendedYieldTime,
|
||||
setCurrentTrackFromLanes,
|
||||
markAllLanesInOrder,
|
||||
logApplyGesturePhase,
|
||||
} from './ReactFiberPerformanceTrack';
|
||||
|
||||
import {
|
||||
@@ -398,7 +399,10 @@ import {
|
||||
} from './ReactFiberRootScheduler';
|
||||
import {getMaskedContext, getUnmaskedContext} from './ReactFiberLegacyContext';
|
||||
import {logUncaughtError} from './ReactFiberErrorLogger';
|
||||
import {stopCommittedGesture} from './ReactFiberGestureScheduler';
|
||||
import {
|
||||
scheduleGestureCommit,
|
||||
stopCommittedGesture,
|
||||
} from './ReactFiberGestureScheduler';
|
||||
import {claimQueuedTransitionTypes} from './ReactFiberTransitionTypes';
|
||||
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
@@ -1542,11 +1546,10 @@ function completeRootWhenReady(
|
||||
isViewTransitionEligible ||
|
||||
(isGestureTransition &&
|
||||
root.pendingGestures !== null &&
|
||||
// If we're committing this gesture and it already has a View Transition
|
||||
// running, then we don't have to wait for that gesture. We'll stop it
|
||||
// when we commit.
|
||||
(root.pendingGestures.running === null ||
|
||||
!root.pendingGestures.committing))
|
||||
// If this gesture already has a View Transition running then we don't
|
||||
// have to wait on that one before proceeding. We may hold the commit
|
||||
// on the gesture committing later on in completeRoot.
|
||||
root.pendingGestures.running === null)
|
||||
) {
|
||||
// Wait for any pending View Transition (including gestures) to finish.
|
||||
suspendOnActiveViewTransition(suspendedState, root.containerInfo);
|
||||
@@ -3463,6 +3466,16 @@ function completeRoot(
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
// Log the previous render phase once we commit. I.e. we weren't interrupted.
|
||||
setCurrentTrackFromLanes(lanes);
|
||||
if (isGestureRender(lanes)) {
|
||||
// Clamp the render start time in case if something else on this lane was committed
|
||||
// (such as this same tree before).
|
||||
if (completedRenderStartTime < gestureClampTime) {
|
||||
completedRenderStartTime = gestureClampTime;
|
||||
}
|
||||
if (completedRenderEndTime < gestureClampTime) {
|
||||
completedRenderEndTime = gestureClampTime;
|
||||
}
|
||||
}
|
||||
if (exitStatus === RootErrored) {
|
||||
logErroredRenderPhase(
|
||||
completedRenderStartTime,
|
||||
@@ -3580,7 +3593,38 @@ function completeRoot(
|
||||
} else {
|
||||
// If we already have a gesture running, we don't update it in place
|
||||
// even if we have a new tree. Instead we wait until we can commit.
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
// Clamp at the render time since we're not going to finish the rest
|
||||
// of this commit or apply yet.
|
||||
finalizeRender(lanes, completedRenderEndTime);
|
||||
}
|
||||
// We are no longer committing.
|
||||
pendingEffectsRoot = (null: any); // Clear for GC purposes.
|
||||
pendingFinishedWork = (null: any); // Clear for GC purposes.
|
||||
pendingEffectsLanes = NoLanes;
|
||||
}
|
||||
// Schedule the root to be committed when the gesture completes.
|
||||
root.cancelPendingCommit = scheduleGestureCommit(
|
||||
committingGesture,
|
||||
completeRoot.bind(
|
||||
null,
|
||||
root,
|
||||
finishedWork,
|
||||
lanes,
|
||||
recoverableErrors,
|
||||
transitions,
|
||||
didIncludeRenderPhaseUpdate,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
didSkipSuspendedSiblings,
|
||||
exitStatus,
|
||||
suspendedState,
|
||||
'Waiting for the Gesture to finish' /* suspendedCommitReason */,
|
||||
completedRenderStartTime,
|
||||
completedRenderEndTime,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4368,6 +4412,15 @@ function flushGestureMutations(): void {
|
||||
ReactSharedInternals.T = prevTransition;
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
recordCommitEndTime();
|
||||
logApplyGesturePhase(
|
||||
pendingEffectsRenderEndTime,
|
||||
commitEndTime,
|
||||
animatingTask,
|
||||
);
|
||||
}
|
||||
|
||||
pendingEffectsStatus = PENDING_GESTURE_ANIMATION_PHASE;
|
||||
}
|
||||
|
||||
@@ -4385,10 +4438,11 @@ function flushGestureAnimations(): void {
|
||||
const lanes = pendingEffectsLanes;
|
||||
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
const startViewTransitionStartTime = commitEndTime;
|
||||
// Update the new commitEndTime to when we started the animation.
|
||||
recordCommitEndTime();
|
||||
logStartViewTransitionYieldPhase(
|
||||
pendingEffectsRenderEndTime,
|
||||
startViewTransitionStartTime,
|
||||
commitEndTime,
|
||||
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
|
||||
animatingTask,
|
||||
@@ -4904,18 +4958,6 @@ export function pingGestureRoot(root: FiberRoot): void {
|
||||
if (gesture === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
root.cancelPendingCommit !== null &&
|
||||
isGestureRender(pendingEffectsLanes)
|
||||
) {
|
||||
// We have a suspended commit which we'll discard and rerender.
|
||||
// TODO: Just use this commit since it's ready to go.
|
||||
const cancelPendingCommit = root.cancelPendingCommit;
|
||||
if (cancelPendingCommit !== null) {
|
||||
root.cancelPendingCommit = null;
|
||||
cancelPendingCommit();
|
||||
}
|
||||
}
|
||||
// Ping it for rerender and commit.
|
||||
markRootPinged(root, GestureLane);
|
||||
ensureRootIsScheduled(root);
|
||||
|
||||
Reference in New Issue
Block a user