Skip to content

Commit 0556bab

Browse files
authored
[Transition Tracing] More Accurate End Time (#25105)
add more accurate end time for transitions and update host configs with `requestPostPaintCallback` function and move post paint logic to another module and use it in the work loop
1 parent 5fdcd23 commit 0556bab

File tree

12 files changed

+367
-31
lines changed

12 files changed

+367
-31
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,7 @@ export function preparePortalMount(portalInstance: any): void {
451451
export function detachDeletedInstance(node: Instance): void {
452452
// noop
453453
}
454+
455+
export function requestPostPaintCallback(callback: (time: number) => void) {
456+
// noop
457+
}

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,10 @@ export const cancelTimeout: any =
377377
typeof clearTimeout === 'function' ? clearTimeout : (undefined: any);
378378
export const noTimeout = -1;
379379
const localPromise = typeof Promise === 'function' ? Promise : undefined;
380-
380+
const localRequestAnimationFrame =
381+
typeof requestAnimationFrame === 'function'
382+
? requestAnimationFrame
383+
: scheduleTimeout;
381384
// -------------------
382385
// Microtasks
383386
// -------------------
@@ -1379,3 +1382,9 @@ export function setupIntersectionObserver(
13791382
},
13801383
};
13811384
}
1385+
1386+
export function requestPostPaintCallback(callback: (time: number) => void) {
1387+
localRequestAnimationFrame(() => {
1388+
localRequestAnimationFrame(time => callback(time));
1389+
});
1390+
}

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,3 +611,7 @@ export function preparePortalMount(portalInstance: Instance): void {
611611
export function detachDeletedInstance(node: Instance): void {
612612
// noop
613613
}
614+
615+
export function requestPostPaintCallback(callback: (time: number) => void) {
616+
// noop
617+
}

packages/react-native-renderer/src/ReactNativeHostConfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,7 @@ export function preparePortalMount(portalInstance: Instance): void {
510510
export function detachDeletedInstance(node: Instance): void {
511511
// noop
512512
}
513+
514+
export function requestPostPaintCallback(callback: (time: number) => void) {
515+
// noop
516+
}

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
473473
logRecoverableError() {
474474
// no-op
475475
},
476+
477+
requestPostPaintCallback(callback) {
478+
const endTime = Scheduler.unstable_now();
479+
callback(endTime);
480+
},
476481
};
477482

478483
const hostConfig = useMutation

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ import {
261261
suspendedThenableDidResolve,
262262
isTrackingSuspendedThenable,
263263
} from './ReactFiberWakeable.new';
264+
import {schedulePostPaintCallback} from './ReactPostPaintCallback';
264265

265266
const ceil = Math.ceil;
266267

@@ -361,6 +362,7 @@ export function getWorkInProgressTransitions() {
361362
}
362363

363364
let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
365+
let currentEndTime: number | null = null;
364366

365367
export function addTransitionStartCallbackToPendingTransition(
366368
transition: Transition,
@@ -2643,6 +2645,36 @@ function commitRootImpl(
26432645
markCommitStopped();
26442646
}
26452647

2648+
if (enableTransitionTracing) {
2649+
// We process transitions during passive effects. However, passive effects can be
2650+
// processed synchronously during the commit phase as well as asynchronously after
2651+
// paint. At the end of the commit phase, we schedule a callback that will be called
2652+
// after the next paint. If the transitions have already been processed (passive
2653+
// effect phase happened synchronously), we will schedule a callback to process
2654+
// the transitions. However, if we don't have any pending transition callbacks, this
2655+
// means that the transitions have yet to be processed (passive effects processed after paint)
2656+
// so we will store the end time of paint so that we can process the transitions
2657+
// and then call the callback via the correct end time.
2658+
const prevRootTransitionCallbacks = root.transitionCallbacks;
2659+
if (prevRootTransitionCallbacks !== null) {
2660+
schedulePostPaintCallback(endTime => {
2661+
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2662+
if (prevPendingTransitionCallbacks !== null) {
2663+
currentPendingTransitionCallbacks = null;
2664+
scheduleCallback(IdleSchedulerPriority, () => {
2665+
processTransitionCallbacks(
2666+
prevPendingTransitionCallbacks,
2667+
endTime,
2668+
prevRootTransitionCallbacks,
2669+
);
2670+
});
2671+
} else {
2672+
currentEndTime = endTime;
2673+
}
2674+
});
2675+
}
2676+
}
2677+
26462678
return null;
26472679
}
26482680

@@ -2784,28 +2816,21 @@ function flushPassiveEffectsImpl() {
27842816
if (enableTransitionTracing) {
27852817
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
27862818
const prevRootTransitionCallbacks = root.transitionCallbacks;
2819+
const prevEndTime = currentEndTime;
27872820
if (
27882821
prevPendingTransitionCallbacks !== null &&
2789-
prevRootTransitionCallbacks !== null
2822+
prevRootTransitionCallbacks !== null &&
2823+
prevEndTime !== null
27902824
) {
2791-
// TODO(luna) Refactor this code into the Host Config
2792-
// TODO(luna) The end time here is not necessarily accurate
2793-
// because passive effects could be called before paint
2794-
// (synchronously) or after paint (normally). We need
2795-
// to come up with a way to get the correct end time for both cases.
2796-
// One solution is in the host config, if the passive effects
2797-
// have not yet been run, make a call to flush the passive effects
2798-
// right after paint.
2799-
const endTime = now();
28002825
currentPendingTransitionCallbacks = null;
2801-
2802-
scheduleCallback(IdleSchedulerPriority, () =>
2826+
currentEndTime = null;
2827+
scheduleCallback(IdleSchedulerPriority, () => {
28032828
processTransitionCallbacks(
28042829
prevPendingTransitionCallbacks,
2805-
endTime,
2830+
prevEndTime,
28062831
prevRootTransitionCallbacks,
2807-
),
2808-
);
2832+
);
2833+
});
28092834
}
28102835
}
28112836

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ import {
261261
suspendedThenableDidResolve,
262262
isTrackingSuspendedThenable,
263263
} from './ReactFiberWakeable.old';
264+
import {schedulePostPaintCallback} from './ReactPostPaintCallback';
264265

265266
const ceil = Math.ceil;
266267

@@ -361,6 +362,7 @@ export function getWorkInProgressTransitions() {
361362
}
362363

363364
let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
365+
let currentEndTime: number | null = null;
364366

365367
export function addTransitionStartCallbackToPendingTransition(
366368
transition: Transition,
@@ -2643,6 +2645,36 @@ function commitRootImpl(
26432645
markCommitStopped();
26442646
}
26452647

2648+
if (enableTransitionTracing) {
2649+
// We process transitions during passive effects. However, passive effects can be
2650+
// processed synchronously during the commit phase as well as asynchronously after
2651+
// paint. At the end of the commit phase, we schedule a callback that will be called
2652+
// after the next paint. If the transitions have already been processed (passive
2653+
// effect phase happened synchronously), we will schedule a callback to process
2654+
// the transitions. However, if we don't have any pending transition callbacks, this
2655+
// means that the transitions have yet to be processed (passive effects processed after paint)
2656+
// so we will store the end time of paint so that we can process the transitions
2657+
// and then call the callback via the correct end time.
2658+
const prevRootTransitionCallbacks = root.transitionCallbacks;
2659+
if (prevRootTransitionCallbacks !== null) {
2660+
schedulePostPaintCallback(endTime => {
2661+
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2662+
if (prevPendingTransitionCallbacks !== null) {
2663+
currentPendingTransitionCallbacks = null;
2664+
scheduleCallback(IdleSchedulerPriority, () => {
2665+
processTransitionCallbacks(
2666+
prevPendingTransitionCallbacks,
2667+
endTime,
2668+
prevRootTransitionCallbacks,
2669+
);
2670+
});
2671+
} else {
2672+
currentEndTime = endTime;
2673+
}
2674+
});
2675+
}
2676+
}
2677+
26462678
return null;
26472679
}
26482680

@@ -2784,28 +2816,21 @@ function flushPassiveEffectsImpl() {
27842816
if (enableTransitionTracing) {
27852817
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
27862818
const prevRootTransitionCallbacks = root.transitionCallbacks;
2819+
const prevEndTime = currentEndTime;
27872820
if (
27882821
prevPendingTransitionCallbacks !== null &&
2789-
prevRootTransitionCallbacks !== null
2822+
prevRootTransitionCallbacks !== null &&
2823+
prevEndTime !== null
27902824
) {
2791-
// TODO(luna) Refactor this code into the Host Config
2792-
// TODO(luna) The end time here is not necessarily accurate
2793-
// because passive effects could be called before paint
2794-
// (synchronously) or after paint (normally). We need
2795-
// to come up with a way to get the correct end time for both cases.
2796-
// One solution is in the host config, if the passive effects
2797-
// have not yet been run, make a call to flush the passive effects
2798-
// right after paint.
2799-
const endTime = now();
28002825
currentPendingTransitionCallbacks = null;
2801-
2802-
scheduleCallback(IdleSchedulerPriority, () =>
2826+
currentEndTime = null;
2827+
scheduleCallback(IdleSchedulerPriority, () => {
28032828
processTransitionCallbacks(
28042829
prevPendingTransitionCallbacks,
2805-
endTime,
2830+
prevEndTime,
28062831
prevRootTransitionCallbacks,
2807-
),
2808-
);
2832+
);
2833+
});
28092834
}
28102835
}
28112836

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
import {requestPostPaintCallback} from './ReactFiberHostConfig';
10+
11+
let postPaintCallbackScheduled = false;
12+
let callbacks = [];
13+
14+
export function schedulePostPaintCallback(callback: (endTime: number) => void) {
15+
callbacks.push(callback);
16+
if (!postPaintCallbackScheduled) {
17+
postPaintCallbackScheduled = true;
18+
requestPostPaintCallback(endTime => {
19+
for (let i = 0; i < callbacks.length; i++) {
20+
callbacks[i](endTime);
21+
}
22+
postPaintCallbackScheduled = false;
23+
callbacks = [];
24+
});
25+
}
26+
}

packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ describe('ReactFiberHostContext', () => {
6767
return DefaultEventPriority;
6868
},
6969
supportsMutation: true,
70+
requestPostPaintCallback: function() {},
7071
});
7172

7273
const container = Renderer.createContainer(
@@ -129,6 +130,7 @@ describe('ReactFiberHostContext', () => {
129130
getCurrentEventPriority: function() {
130131
return DefaultEventPriority;
131132
},
133+
requestPostPaintCallback: function() {},
132134
supportsMutation: true,
133135
});
134136

0 commit comments

Comments
 (0)