Skip to content

Commit 4cf9063

Browse files
authored
Optimize gesture by allowing the original work in progress tree to be a suspended commit (facebook#35510)
Stacked on facebook#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.)
1 parent eac3c95 commit 4cf9063

File tree

4 files changed

+125
-27
lines changed

4 files changed

+125
-27
lines changed

fixtures/view-transition/src/components/SwipeRecognizer.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,17 @@ export default function SwipeRecognizer({
114114
);
115115
}
116116
function onGestureEnd(changed) {
117-
// Reset scroll
118-
if (changed) {
119-
// Trigger side-effects
120-
startTransition(action);
121-
}
117+
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
118+
// before scheduling new updates.
122119
if (activeGesture.current !== null) {
123120
const cancelGesture = activeGesture.current;
124121
activeGesture.current = null;
125122
cancelGesture();
126123
}
124+
if (changed) {
125+
// Trigger side-effects
126+
startTransition(action);
127+
}
127128
}
128129
function onScrollEnd() {
129130
if (touchTimeline.current) {

packages/react-reconciler/src/ReactFiberGestureScheduler.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type ScheduledGesture = {
3535
rangeEnd: number, // The percentage along the timeline where the "destination" state is reached.
3636
types: null | TransitionTypes, // Any addTransitionType call made during startGestureTransition.
3737
running: null | RunningViewTransition, // Used to cancel the running transition after we're done.
38+
commit: null | (() => void), // Callback to run to commit if there's a pending commit.
3839
committing: boolean, // If the gesture was released in a committed state and should actually commit.
3940
revertLane: Lane, // The Lane that we'll use to schedule the revert.
4041
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
@@ -64,6 +65,7 @@ export function scheduleGesture(
6465
rangeEnd: 100, // Uninitialized
6566
types: null,
6667
running: null,
68+
commit: null,
6769
committing: false,
6870
revertLane: NoLane, // Starts uninitialized.
6971
prev: prev,
@@ -164,9 +166,17 @@ export function cancelScheduledGesture(
164166
// lane to actually commit it.
165167
gesture.committing = true;
166168
if (root.pendingGestures === gesture) {
167-
// Ping the root given the new state. This is similar to pingSuspendedRoot.
168-
// This will either schedule the gesture lane to be committed possibly from its current state.
169-
pingGestureRoot(root);
169+
const commitCallback = gesture.commit;
170+
if (commitCallback !== null) {
171+
gesture.commit = null;
172+
// If we already have a commit prepared we can immediately commit the tree
173+
// without rerendering.
174+
// TODO: Consider scheduling this in a task instead of synchronously inside the last cancellation.s
175+
commitCallback();
176+
} else {
177+
// Ping the root given the new state. This is similar to pingSuspendedRoot.
178+
pingGestureRoot(root);
179+
}
170180
}
171181
} else {
172182
// If we're not going to commit this gesture we can stop the View Transition
@@ -235,3 +245,13 @@ export function stopCommittedGesture(root: FiberRoot) {
235245
}
236246
}
237247
}
248+
249+
export function scheduleGestureCommit(
250+
gesture: ScheduledGesture,
251+
callback: () => void,
252+
): () => void {
253+
gesture.commit = callback;
254+
return function () {
255+
gesture.commit = null;
256+
};
257+
}

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,41 @@ export function logPaintYieldPhase(
15691569
}
15701570
}
15711571

1572+
export function logApplyGesturePhase(
1573+
startTime: number,
1574+
endTime: number,
1575+
debugTask: null | ConsoleTask,
1576+
): void {
1577+
if (supportsUserTiming) {
1578+
if (endTime <= startTime) {
1579+
return;
1580+
}
1581+
if (__DEV__ && debugTask) {
1582+
debugTask.run(
1583+
// $FlowFixMe[method-unbinding]
1584+
console.timeStamp.bind(
1585+
console,
1586+
'Create Ghost Tree',
1587+
startTime,
1588+
endTime,
1589+
currentTrack,
1590+
LANES_TRACK_GROUP,
1591+
'secondary-dark',
1592+
),
1593+
);
1594+
} else {
1595+
console.timeStamp(
1596+
'Create Ghost Tree',
1597+
startTime,
1598+
endTime,
1599+
currentTrack,
1600+
LANES_TRACK_GROUP,
1601+
'secondary-dark',
1602+
);
1603+
}
1604+
}
1605+
}
1606+
15721607
export function logStartViewTransitionYieldPhase(
15731608
startTime: number,
15741609
endTime: number,

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import {
9292
logSuspendedYieldTime,
9393
setCurrentTrackFromLanes,
9494
markAllLanesInOrder,
95+
logApplyGesturePhase,
9596
} from './ReactFiberPerformanceTrack';
9697

9798
import {
@@ -398,7 +399,10 @@ import {
398399
} from './ReactFiberRootScheduler';
399400
import {getMaskedContext, getUnmaskedContext} from './ReactFiberLegacyContext';
400401
import {logUncaughtError} from './ReactFiberErrorLogger';
401-
import {stopCommittedGesture} from './ReactFiberGestureScheduler';
402+
import {
403+
scheduleGestureCommit,
404+
stopCommittedGesture,
405+
} from './ReactFiberGestureScheduler';
402406
import {claimQueuedTransitionTypes} from './ReactFiberTransitionTypes';
403407

404408
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
@@ -1542,11 +1546,10 @@ function completeRootWhenReady(
15421546
isViewTransitionEligible ||
15431547
(isGestureTransition &&
15441548
root.pendingGestures !== null &&
1545-
// If we're committing this gesture and it already has a View Transition
1546-
// running, then we don't have to wait for that gesture. We'll stop it
1547-
// when we commit.
1548-
(root.pendingGestures.running === null ||
1549-
!root.pendingGestures.committing))
1549+
// If this gesture already has a View Transition running then we don't
1550+
// have to wait on that one before proceeding. We may hold the commit
1551+
// on the gesture committing later on in completeRoot.
1552+
root.pendingGestures.running === null)
15501553
) {
15511554
// Wait for any pending View Transition (including gestures) to finish.
15521555
suspendOnActiveViewTransition(suspendedState, root.containerInfo);
@@ -3463,6 +3466,16 @@ function completeRoot(
34633466
if (enableProfilerTimer && enableComponentPerformanceTrack) {
34643467
// Log the previous render phase once we commit. I.e. we weren't interrupted.
34653468
setCurrentTrackFromLanes(lanes);
3469+
if (isGestureRender(lanes)) {
3470+
// Clamp the render start time in case if something else on this lane was committed
3471+
// (such as this same tree before).
3472+
if (completedRenderStartTime < gestureClampTime) {
3473+
completedRenderStartTime = gestureClampTime;
3474+
}
3475+
if (completedRenderEndTime < gestureClampTime) {
3476+
completedRenderEndTime = gestureClampTime;
3477+
}
3478+
}
34663479
if (exitStatus === RootErrored) {
34673480
logErroredRenderPhase(
34683481
completedRenderStartTime,
@@ -3580,7 +3593,38 @@ function completeRoot(
35803593
} else {
35813594
// If we already have a gesture running, we don't update it in place
35823595
// even if we have a new tree. Instead we wait until we can commit.
3596+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3597+
// Clamp at the render time since we're not going to finish the rest
3598+
// of this commit or apply yet.
3599+
finalizeRender(lanes, completedRenderEndTime);
3600+
}
3601+
// We are no longer committing.
3602+
pendingEffectsRoot = (null: any); // Clear for GC purposes.
3603+
pendingFinishedWork = (null: any); // Clear for GC purposes.
3604+
pendingEffectsLanes = NoLanes;
35833605
}
3606+
// Schedule the root to be committed when the gesture completes.
3607+
root.cancelPendingCommit = scheduleGestureCommit(
3608+
committingGesture,
3609+
completeRoot.bind(
3610+
null,
3611+
root,
3612+
finishedWork,
3613+
lanes,
3614+
recoverableErrors,
3615+
transitions,
3616+
didIncludeRenderPhaseUpdate,
3617+
spawnedLane,
3618+
updatedLanes,
3619+
suspendedRetryLanes,
3620+
didSkipSuspendedSiblings,
3621+
exitStatus,
3622+
suspendedState,
3623+
'Waiting for the Gesture to finish' /* suspendedCommitReason */,
3624+
completedRenderStartTime,
3625+
completedRenderEndTime,
3626+
),
3627+
);
35843628
return;
35853629
}
35863630
}
@@ -4368,6 +4412,15 @@ function flushGestureMutations(): void {
43684412
ReactSharedInternals.T = prevTransition;
43694413
}
43704414

4415+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
4416+
recordCommitEndTime();
4417+
logApplyGesturePhase(
4418+
pendingEffectsRenderEndTime,
4419+
commitEndTime,
4420+
animatingTask,
4421+
);
4422+
}
4423+
43714424
pendingEffectsStatus = PENDING_GESTURE_ANIMATION_PHASE;
43724425
}
43734426

@@ -4385,10 +4438,11 @@ function flushGestureAnimations(): void {
43854438
const lanes = pendingEffectsLanes;
43864439

43874440
if (enableProfilerTimer && enableComponentPerformanceTrack) {
4441+
const startViewTransitionStartTime = commitEndTime;
43884442
// Update the new commitEndTime to when we started the animation.
43894443
recordCommitEndTime();
43904444
logStartViewTransitionYieldPhase(
4391-
pendingEffectsRenderEndTime,
4445+
startViewTransitionStartTime,
43924446
commitEndTime,
43934447
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
43944448
animatingTask,
@@ -4904,18 +4958,6 @@ export function pingGestureRoot(root: FiberRoot): void {
49044958
if (gesture === null) {
49054959
return;
49064960
}
4907-
if (
4908-
root.cancelPendingCommit !== null &&
4909-
isGestureRender(pendingEffectsLanes)
4910-
) {
4911-
// We have a suspended commit which we'll discard and rerender.
4912-
// TODO: Just use this commit since it's ready to go.
4913-
const cancelPendingCommit = root.cancelPendingCommit;
4914-
if (cancelPendingCommit !== null) {
4915-
root.cancelPendingCommit = null;
4916-
cancelPendingCommit();
4917-
}
4918-
}
49194961
// Ping it for rerender and commit.
49204962
markRootPinged(root, GestureLane);
49214963
ensureRootIsScheduled(root);

0 commit comments

Comments
 (0)