Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dfc3894
WIP
jgonet Feb 7, 2024
2c21257
WIP
jgonet 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
11 changes: 11 additions & 0 deletions app/src/examples/PerfomanceMonitorExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { View } from 'react-native';
import { PerformanceMonitor } from 'react-native-reanimated';

export default function PerformanceMonitorExample() {
return (
<View>
<PerformanceMonitor />
</View>
);
}
Comment thread
latekvo marked this conversation as resolved.
Outdated
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';

interface Example {
icon?: string;
Expand Down Expand Up @@ -481,6 +482,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
203 changes: 203 additions & 0 deletions src/reanimated2/component/PerformanceMonitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
'use strict';

import React, { useEffect, useRef } from 'react';
import { Text, 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): string {
'worklet';
return (1000 / renderTimeInMs).toFixed(1);
}

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>
) {
'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 = createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE);

useEffect(() => {
loopAnimationFrame((lastTime, time) => {
const currentFps = completeBufferRoutine(
circularBuffer,
time,
lastTime,
totalRenderTime
);

jsFps.value = currentFps;
});
});

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

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

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

useFrameCallback(({ timestamp }: FrameInfo) => {
if (circularBuffer.current === null) {
circularBuffer.current = createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE);
}
const previousTimestamp = circularBuffer.current.front() ?? timestamp;
const currentFps = completeBufferRoutine(
circularBuffer.current,
timestamp,
previousTimestamp,
totalRenderTime
);
uiFps.value = currentFps;
});

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

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

export function PerformanceMonitor() {
return (
<View style={[styles.monitor]}>
Comment thread
latekvo marked this conversation as resolved.
Outdated
<JsPerformance />
<UiPerformance />
</View>
);
}

const styles = StyleSheet.create({
monitor: {
flexDirection: 'row',
gap: 8,
borderWidth: 1,
padding: 8,
position: 'absolute',
backgroundColor: '#fffa',
zIndex: 1000,
},
headers: {
fontSize: 12,
},
text: {
fontSize: 16,
},
container: {
width: 40,
alignItems: 'center',
justifyContent: 'center',
},
});
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