Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FPSCounter #5770

Merged
merged 25 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dfc3894
WIP
jakub-gonet Feb 7, 2024
2c21257
WIP
jakub-gonet Feb 15, 2024
6f658ef
Merge branch 'main' into jgonet/fps
piaskowyk Mar 8, 2024
08f8554
Restore Podfile.lock
piaskowyk Mar 8, 2024
5e9ec74
fix floating point error
latekvo Mar 8, 2024
f69861f
Add circularBuffer to JS fps counter to avoid fps spikes.
latekvo Mar 11, 2024
89a823f
Merge branch 'main' into jgonet/fps
latekvo Mar 11, 2024
ce2e981
Visual improvements, remove jittering. Ran prettier.
latekvo Mar 11, 2024
74d7814
Extracted ui and js logic into a separate function.
latekvo Mar 15, 2024
b1611bf
save before refactor
latekvo Mar 18, 2024
0633742
Moved performance monitor to a separate component.
latekvo Mar 18, 2024
d98a95b
cleaned up code
latekvo Mar 19, 2024
be0d2d8
split imports to fix compilation errors
latekvo Mar 19, 2024
64a90a3
Merge branch 'main' into jgonet/fps
latekvo Mar 19, 2024
be654fe
fixed import statements
latekvo Mar 19, 2024
b44f1f8
Merge branch 'jgonet/fps' of https://github.com/software-mansion/reac…
latekvo Mar 19, 2024
f1a715b
resolve circular dependencies
latekvo Mar 19, 2024
4b2e2ee
add 'use strict' statement
latekvo Mar 19, 2024
e40ba7f
added heavy apps to performance monitor example
latekvo Mar 21, 2024
2ecbc48
fix ui measurement errors
latekvo Mar 25, 2024
3d3fea5
fixed JS loops rerunning after each rerender
latekvo Mar 25, 2024
8016d92
Merge branch 'main' into jgonet/fps
latekvo Mar 25, 2024
95f8f64
fix styling and eslint errors
latekvo Mar 25, 2024
17249c5
Merge branch 'jgonet/fps' of https://github.com/software-mansion/reac…
latekvo Mar 25, 2024
18cd82b
apply review suggestions, style improvements
latekvo Mar 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions app/src/examples/PerfomanceMonitorExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useRef, useState } from 'react';
import { Text, StyleSheet, Pressable, View } from 'react-native';
import { PerformanceMonitor } from 'react-native-reanimated';

import EmptyExample from './EmptyExample';
import BokehExample from './BokehExample';
import PlanetsExample from './PlanetsExample';
import EmojiWaterfallExample from './EmojiWaterfallExample';

enum Examples {
Empty = 'Empty Example',
Bokeh = 'Bokeh Example',
Planets = 'Planets Example',
Emojis = 'Emoji Waterfall Example',
}

export default function PerformanceMonitorExample() {
const exampleElements = useRef(
new Map<Examples, JSX.Element>([
[Examples.Empty, <EmptyExample />],
[Examples.Bokeh, <BokehExample />],
[Examples.Planets, <PlanetsExample />],
[Examples.Emojis, <EmojiWaterfallExample />],
])
);

const [currentExample, setCurrentExample] = useState(Examples.Empty);

return (
<>
<PerformanceMonitor />
{exampleElements.current.get(currentExample)!}
<View style={styles.buttonContainer}>
{[
Examples.Empty,
Examples.Bokeh,
Examples.Planets,
Examples.Emojis,
].map((element) => (
<Pressable
key={element}
style={styles.button}
onPress={() => setCurrentExample(element)}>
<Text>{element}</Text>
</Pressable>
))}
</View>
</>
);
}

const styles = StyleSheet.create({
buttonContainer: {
position: 'absolute',
flex: 1,
flexDirection: 'column',
gap: 4,
margin: 8,
marginLeft: 200,
},
button: {
backgroundColor: 'lightblue',
padding: 8,
borderRadius: 8,
flex: 1,
textAlign: 'center',
},
});
6 changes: 6 additions & 0 deletions app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ import WorkletFactoryCrash from './WorkletFactoryCrashExample';
import RuntimeTestsExample from './RuntimeTests/RuntimeTestsExample';
import HabitsExample from './LayoutAnimations/HabitsExample';
import MemoExample from './MemoExample';
import PerformanceMonitorExample from './PerfomanceMonitorExample';
import ScreenTransitionExample from './ScreenTransitionExample';

interface Example {
Expand Down Expand Up @@ -486,6 +487,11 @@ export const EXAMPLES: Record<string, Example> = {
title: 'Habits',
screen: HabitsExample,
},
PerformanceMonitorExample: {
icon: '⏱️',
title: 'Performance monitor',
screen: PerformanceMonitorExample,
},

// Old examples

Expand Down
4 changes: 2 additions & 2 deletions src/createAnimatedComponent/PropsFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

import { shallowEqual } from '../reanimated2/hook/utils';
import type { StyleProps } from '../reanimated2';
import { isSharedValue } from '../reanimated2';
import type { StyleProps } from '../reanimated2/commonTypes';
import { isSharedValue } from '../reanimated2/isSharedValue';
import { isChromeDebugger } from '../reanimated2/PlatformChecker';
import WorkletEventHandler from '../reanimated2/WorkletEventHandler';
import { initialUpdaterRun } from '../reanimated2/animation';
Expand Down
216 changes: 216 additions & 0 deletions src/reanimated2/component/PerformanceMonitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
'use strict';

import React, { useEffect, useRef } from 'react';
import { TextInput, StyleSheet, View } from 'react-native';

import type { FrameInfo } from '../frameCallback';
import type { SharedValue } from '../commonTypes';
import { useSharedValue, useAnimatedProps, useFrameCallback } from '../hook';
import { createAnimatedComponent } from '../../createAnimatedComponent';
import { addWhitelistedNativeProps } from '../../ConfigHelper';

type CircularBuffer = ReturnType<typeof createCircularDoublesBuffer>;
function createCircularDoublesBuffer(size: number) {
'worklet';

return {
next: 0 as number,
buffer: new Float32Array(size),
size,
count: 0 as number,

push(value: number): number | null {
const oldValue = this.buffer[this.next];
const oldCount = this.count;
this.buffer[this.next] = value;

this.next = (this.next + 1) % this.size;
this.count = Math.min(this.size, this.count + 1);
return oldCount === this.size ? oldValue : null;
},

front(): number | null {
const notEmpty = this.count > 0;
if (notEmpty) {
const current = this.next - 1;
const index = current < 0 ? this.size - 1 : current;
return this.buffer[index];
}
return null;
},

back(): number | null {
const notEmpty = this.count > 0;
return notEmpty ? this.buffer[this.next] : null;
},
};
}

const DEFAULT_BUFFER_SIZE = 60;
addWhitelistedNativeProps({ text: true });
const AnimatedTextInput = createAnimatedComponent(TextInput);

function loopAnimationFrame(fn: (lastTime: number, time: number) => void) {
let lastTime = 0;

function loop() {
requestAnimationFrame((time) => {
if (lastTime > 0) {
fn(lastTime, time);
}
lastTime = time;
requestAnimationFrame(loop);
});
}

loop();
}

function getFps(renderTimeInMs: number): number {
'worklet';
return 1000 / renderTimeInMs;
}

function getTimeDelta(
timestamp: number,
previousTimestamp: number | null
): number {
'worklet';
return previousTimestamp !== null ? timestamp - previousTimestamp : 0;
}

function completeBufferRoutine(
buffer: CircularBuffer,
timestamp: number,
previousTimestamp: number,
totalRenderTime: SharedValue<number>
): number {
'worklet';
timestamp = Math.round(timestamp);
previousTimestamp = Math.round(previousTimestamp) ?? timestamp;

const droppedTimestamp = buffer.push(timestamp);
const nextToDrop = buffer.back()!;

const delta = getTimeDelta(timestamp, previousTimestamp);
const droppedDelta = getTimeDelta(nextToDrop, droppedTimestamp);

totalRenderTime.value += delta - droppedDelta;

return getFps(totalRenderTime.value / buffer.count);
}

function JsPerformance() {
const jsFps = useSharedValue<string | null>(null);
const totalRenderTime = useSharedValue(0);
const circularBuffer = useRef<CircularBuffer>(
createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE)
);

useEffect(() => {
loopAnimationFrame((_, timestamp) => {
timestamp = Math.round(timestamp);
const previousTimestamp = circularBuffer.current.front() ?? timestamp;

const currentFps = completeBufferRoutine(
circularBuffer.current,
timestamp,
previousTimestamp,
totalRenderTime
);

// JS fps have to be measured every 2nd frame,
// thus 2x multiplication has to occur here
jsFps.value = (currentFps * 2).toFixed(0);
});
}, []);

const animatedProps = useAnimatedProps(() => {
const text = 'JS: ' + jsFps.value ?? 'N/A';
return { text, defaultValue: text };
});

return (
<View style={styles.container}>
<AnimatedTextInput
style={styles.text}
animatedProps={animatedProps}
editable={false}
/>
</View>
);
}

function UiPerformance() {
const uiFps = useSharedValue<string | null>(null);
const totalRenderTime = useSharedValue(0);
const circularBuffer = useSharedValue<CircularBuffer | null>(null);

useFrameCallback(({ timestamp }: FrameInfo) => {
if (circularBuffer.value === null) {
circularBuffer.value = createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE);
}

timestamp = Math.round(timestamp);
const previousTimestamp = circularBuffer.value.front() ?? timestamp;

const currentFps = completeBufferRoutine(
circularBuffer.value,
timestamp,
previousTimestamp,
totalRenderTime
);

uiFps.value = currentFps.toFixed(0);
});

const animatedProps = useAnimatedProps(() => {
const text = 'UI: ' + uiFps.value ?? 'N/A';
return { text, defaultValue: text };
});

return (
<View style={styles.container}>
<AnimatedTextInput
style={styles.text}
animatedProps={animatedProps}
editable={false}
/>
</View>
);
}

export function PerformanceMonitor() {
return (
<View style={styles.monitor}>
<JsPerformance />
<UiPerformance />
</View>
);
}

const styles = StyleSheet.create({
monitor: {
flexDirection: 'row',
position: 'absolute',
backgroundColor: '#0006',
zIndex: 1000,
},
header: {
fontSize: 14,
color: '#ffff',
paddingHorizontal: 5,
},
text: {
fontSize: 13,
color: '#ffff',
fontFamily: 'monospace',
paddingHorizontal: 3,
},
container: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
},
});
1 change: 1 addition & 0 deletions src/reanimated2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export {
getAnimatedStyle,
} from './jestUtils';
export { LayoutAnimationConfig } from './component/LayoutAnimationConfig';
export { PerformanceMonitor } from './component/PerformanceMonitor';
export type {
Adaptable,
AdaptTransforms,
Expand Down