mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
Summary: Pull Request resolved: https://github.com/react/react-native/pull/57417 `SceneTracker` was a small helper in `Libraries/Utilities` that tracked the active navigation scene. It was only ever consumed through a deep import and was never part of the public API. This removes it from the React Native package and moves scene tracking to the application layer that actually depends on it. As part of this, `AppRegistry.runApplication` no longer sets the active scene to the app key. To let the application layer preserve that behavior for surfaces launched by app key, `WrapperComponentProvider` now receives the app key as an optional second argument. Changelog: [General][Breaking] - Remove the `SceneTracker` module from `Libraries/Utilities`, stop setting the active scene from `AppRegistry.runApplication`, and pass the app key as an optional second argument to `WrapperComponentProvider` Reviewed By: huntie, javache Differential Revision: D110471317 fbshipit-source-id: 28d44c74d2cff7926094959f26aa1ed0bf4be9ad
256 lines
7.9 KiB
JavaScript
256 lines
7.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {CellMetricProps} from './ListMetricsAggregator';
|
|
|
|
import ListMetricsAggregator from './ListMetricsAggregator';
|
|
|
|
export type FillRateInfo = Info;
|
|
|
|
class Info {
|
|
any_blank_count: number = 0;
|
|
any_blank_ms: number = 0;
|
|
any_blank_speed_sum: number = 0;
|
|
mostly_blank_count: number = 0;
|
|
mostly_blank_ms: number = 0;
|
|
pixels_blank: number = 0;
|
|
pixels_sampled: number = 0;
|
|
pixels_scrolled: number = 0;
|
|
total_time_spent: number = 0;
|
|
sample_count: number = 0;
|
|
}
|
|
|
|
const DEBUG = false;
|
|
|
|
let _listeners: Array<(Info) => void> = [];
|
|
let _minSampleCount = 10;
|
|
/* $FlowFixMe[constant-condition] Error discovered during Constant Condition
|
|
* roll out. See https://fburl.com/workplace/1v97vimq. */
|
|
let _sampleRate = DEBUG ? 1 : null;
|
|
|
|
/**
|
|
* A helper class for detecting when the maximem fill rate of `VirtualizedList` is exceeded.
|
|
* By default the sampling rate is set to zero and this will do nothing. If you want to collect
|
|
* samples (e.g. to log them), make sure to call `FillRateHelper.setSampleRate(0.0-1.0)`.
|
|
*
|
|
* Listeners and sample rate are global for all `VirtualizedList`s - typical usage will combine with
|
|
* the active scene/route to determine the context of the events.
|
|
*/
|
|
class FillRateHelper {
|
|
_anyBlankStartTime: ?number = null;
|
|
_enabled = false;
|
|
_listMetrics: ListMetricsAggregator;
|
|
_info: Info = new Info();
|
|
_mostlyBlankStartTime: ?number = null;
|
|
_samplesStartTime: ?number = null;
|
|
|
|
static addListener(callback: FillRateInfo => void): {
|
|
remove: () => void,
|
|
...
|
|
} {
|
|
if (_sampleRate === null) {
|
|
console.warn('Call `FillRateHelper.setSampleRate` before `addListener`.');
|
|
}
|
|
_listeners.push(callback);
|
|
return {
|
|
remove: () => {
|
|
_listeners = _listeners.filter(listener => callback !== listener);
|
|
},
|
|
};
|
|
}
|
|
|
|
static setSampleRate(sampleRate: number) {
|
|
_sampleRate = sampleRate;
|
|
}
|
|
|
|
static setMinSampleCount(minSampleCount: number) {
|
|
_minSampleCount = minSampleCount;
|
|
}
|
|
|
|
constructor(listMetrics: ListMetricsAggregator) {
|
|
this._listMetrics = listMetrics;
|
|
this._enabled = (_sampleRate || 0) > Math.random();
|
|
this._resetData();
|
|
}
|
|
|
|
activate() {
|
|
if (this._enabled && this._samplesStartTime == null) {
|
|
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
|
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
|
DEBUG && console.debug('FillRateHelper: activate');
|
|
this._samplesStartTime = global.performance.now();
|
|
}
|
|
}
|
|
|
|
deactivateAndFlush() {
|
|
if (!this._enabled) {
|
|
return;
|
|
}
|
|
const start = this._samplesStartTime; // const for flow
|
|
if (start == null) {
|
|
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
|
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
|
DEBUG &&
|
|
console.debug('FillRateHelper: bail on deactivate with no start time');
|
|
return;
|
|
}
|
|
if (this._info.sample_count < _minSampleCount) {
|
|
// Don't bother with under-sampled events.
|
|
this._resetData();
|
|
return;
|
|
}
|
|
const total_time_spent = global.performance.now() - start;
|
|
const info: any = {
|
|
...this._info,
|
|
total_time_spent,
|
|
};
|
|
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
|
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
|
if (DEBUG) {
|
|
const derived = {
|
|
avg_blankness: this._info.pixels_blank / this._info.pixels_sampled,
|
|
avg_speed: this._info.pixels_scrolled / (total_time_spent / 1000),
|
|
avg_speed_when_any_blank:
|
|
this._info.any_blank_speed_sum / this._info.any_blank_count,
|
|
any_blank_per_min:
|
|
this._info.any_blank_count / (total_time_spent / 1000 / 60),
|
|
any_blank_time_frac: this._info.any_blank_ms / total_time_spent,
|
|
mostly_blank_per_min:
|
|
this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
|
|
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent,
|
|
};
|
|
for (const key in derived) {
|
|
// $FlowFixMe[prop-missing]
|
|
// $FlowFixMe[invalid-computed-prop]
|
|
derived[key] = Math.round(1000 * derived[key]) / 1000;
|
|
}
|
|
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info});
|
|
}
|
|
_listeners.forEach(listener => listener(info));
|
|
this._resetData();
|
|
}
|
|
|
|
computeBlankness(
|
|
props: {
|
|
...CellMetricProps,
|
|
initialNumToRender?: ?number,
|
|
...
|
|
},
|
|
cellsAroundViewport: {
|
|
first: number,
|
|
last: number,
|
|
...
|
|
},
|
|
scrollMetrics: {
|
|
dOffset: number,
|
|
offset: number,
|
|
velocity: number,
|
|
visibleLength: number,
|
|
...
|
|
},
|
|
): number {
|
|
if (
|
|
!this._enabled ||
|
|
props.getItemCount(props.data) === 0 ||
|
|
cellsAroundViewport.last < cellsAroundViewport.first ||
|
|
this._samplesStartTime == null
|
|
) {
|
|
return 0;
|
|
}
|
|
const {dOffset, offset, velocity, visibleLength} = scrollMetrics;
|
|
|
|
// Denominator metrics that we track for all events - most of the time there is no blankness and
|
|
// we want to capture that.
|
|
this._info.sample_count++;
|
|
this._info.pixels_sampled += Math.round(visibleLength);
|
|
this._info.pixels_scrolled += Math.round(Math.abs(dOffset));
|
|
const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec
|
|
|
|
// Whether blank now or not, record the elapsed time blank if we were blank last time.
|
|
const now = global.performance.now();
|
|
if (this._anyBlankStartTime != null) {
|
|
this._info.any_blank_ms += now - this._anyBlankStartTime;
|
|
}
|
|
this._anyBlankStartTime = null;
|
|
if (this._mostlyBlankStartTime != null) {
|
|
this._info.mostly_blank_ms += now - this._mostlyBlankStartTime;
|
|
}
|
|
this._mostlyBlankStartTime = null;
|
|
|
|
let blankTop = 0;
|
|
let first = cellsAroundViewport.first;
|
|
let firstFrame = this._listMetrics.getCellMetrics(first, props);
|
|
while (
|
|
first <= cellsAroundViewport.last &&
|
|
(!firstFrame || !firstFrame.isMounted)
|
|
) {
|
|
firstFrame = this._listMetrics.getCellMetrics(first, props);
|
|
first++;
|
|
}
|
|
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header
|
|
// as blank.
|
|
if (firstFrame && first > 0) {
|
|
blankTop = Math.min(
|
|
visibleLength,
|
|
Math.max(0, firstFrame.offset - offset),
|
|
);
|
|
}
|
|
let blankBottom = 0;
|
|
let last = cellsAroundViewport.last;
|
|
let lastFrame = this._listMetrics.getCellMetrics(last, props);
|
|
while (
|
|
last >= cellsAroundViewport.first &&
|
|
(!lastFrame || !lastFrame.isMounted)
|
|
) {
|
|
lastFrame = this._listMetrics.getCellMetrics(last, props);
|
|
last--;
|
|
}
|
|
// Only count blankBottom if we aren't rendering the last item, otherwise we will count the
|
|
// footer as blank.
|
|
if (lastFrame && last < props.getItemCount(props.data) - 1) {
|
|
const bottomEdge = lastFrame.offset + lastFrame.length;
|
|
blankBottom = Math.min(
|
|
visibleLength,
|
|
Math.max(0, offset + visibleLength - bottomEdge),
|
|
);
|
|
}
|
|
const pixels_blank = Math.round(blankTop + blankBottom);
|
|
const blankness = pixels_blank / visibleLength;
|
|
if (blankness > 0) {
|
|
this._anyBlankStartTime = now;
|
|
this._info.any_blank_speed_sum += scrollSpeed;
|
|
this._info.any_blank_count++;
|
|
this._info.pixels_blank += pixels_blank;
|
|
if (blankness > 0.5) {
|
|
this._mostlyBlankStartTime = now;
|
|
this._info.mostly_blank_count++;
|
|
}
|
|
} else if (scrollSpeed < 0.01 || Math.abs(dOffset) < 1) {
|
|
this.deactivateAndFlush();
|
|
}
|
|
return blankness;
|
|
}
|
|
|
|
enabled(): boolean {
|
|
return this._enabled;
|
|
}
|
|
|
|
_resetData() {
|
|
this._anyBlankStartTime = null;
|
|
this._info = new Info();
|
|
this._mostlyBlankStartTime = null;
|
|
this._samplesStartTime = null;
|
|
}
|
|
}
|
|
|
|
export default FillRateHelper;
|