@vyshnav18/react-native-fps-counter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +101 -0
  2. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  3. package/android/.gradle/9.2.0/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/9.2.0/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  6. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  7. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/9.2.0/gc.properties +0 -0
  9. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  10. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  11. package/android/.gradle/vcs-1/gc.properties +0 -0
  12. package/android/build/reports/problems/problems-report.html +659 -0
  13. package/android/build.gradle +64 -0
  14. package/android/src/main/AndroidManifest.xml +3 -0
  15. package/android/src/main/java/com/rnfpscounter/FpsCounterModule.kt +120 -0
  16. package/android/src/main/java/com/rnfpscounter/FpsCounterPackage.kt +16 -0
  17. package/dist/index.d.mts +110 -0
  18. package/dist/index.d.ts +110 -0
  19. package/dist/index.js +192 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +188 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/ios/FpsCounterModule.h +12 -0
  24. package/ios/FpsCounterModule.mm +123 -0
  25. package/ios/RNFpsCounter.podspec +21 -0
  26. package/package.json +73 -0
  27. package/react-native.config.js +14 -0
  28. package/src/FpsCounter.tsx +190 -0
  29. package/src/index.ts +10 -0
  30. package/src/specs/NativeFpsCounter.ts +26 -0
  31. package/src/useFPSMetric.ts +82 -0
  32. package/src/useUIFPSMetric.ts +81 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,188 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { NativeModules, StyleSheet, NativeEventEmitter, View, Text } from 'react-native';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/useFPSMetric.ts
6
+ function useFPSMetric() {
7
+ const [frameState, setFrameState] = useState({
8
+ fps: 0,
9
+ lastStamp: Date.now(),
10
+ framesCount: 0,
11
+ average: 0,
12
+ totalCount: 0
13
+ });
14
+ useEffect(() => {
15
+ let timeout = null;
16
+ requestAnimationFrame(() => {
17
+ timeout = setTimeout(() => {
18
+ const currentStamp = Date.now();
19
+ const shouldSetState = currentStamp - frameState.lastStamp > 1e3;
20
+ const newFramesCount = frameState.framesCount + 1;
21
+ if (shouldSetState) {
22
+ const newValue = frameState.framesCount;
23
+ const totalCount = frameState.totalCount + 1;
24
+ const newMean = Math.min(
25
+ frameState.average + (newValue - frameState.average) / totalCount,
26
+ 60
27
+ );
28
+ setFrameState({
29
+ fps: frameState.framesCount,
30
+ lastStamp: currentStamp,
31
+ framesCount: 0,
32
+ average: newMean,
33
+ totalCount
34
+ });
35
+ } else {
36
+ setFrameState({
37
+ ...frameState,
38
+ framesCount: newFramesCount
39
+ });
40
+ }
41
+ }, 0);
42
+ });
43
+ return () => {
44
+ if (timeout) clearTimeout(timeout);
45
+ };
46
+ }, [frameState]);
47
+ return { average: frameState.average, fps: frameState.fps };
48
+ }
49
+ var FpsCounterModule = NativeModules.FpsCounter;
50
+ function useUIFPSMetric() {
51
+ const [frameData, setFrameData] = useState({
52
+ uiFps: 0,
53
+ uiAverage: 0,
54
+ isAvailable: !!FpsCounterModule
55
+ });
56
+ const eventEmitterRef = useRef(null);
57
+ useEffect(() => {
58
+ if (!FpsCounterModule) {
59
+ if (__DEV__) {
60
+ console.warn(
61
+ "[react-native-fps-counter] Native module not found.\nMake sure you have run:\n - iOS: cd ios && pod install\n - Android: Rebuild the app (npx react-native run-android)\nIf using Expo, you need to create a development build."
62
+ );
63
+ }
64
+ return;
65
+ }
66
+ eventEmitterRef.current = new NativeEventEmitter(FpsCounterModule);
67
+ const subscription = eventEmitterRef.current.addListener(
68
+ "onUIFpsUpdate",
69
+ (data) => {
70
+ setFrameData({
71
+ uiFps: Math.round(data.fps),
72
+ uiAverage: data.average,
73
+ isAvailable: true
74
+ });
75
+ }
76
+ );
77
+ FpsCounterModule.startMonitoring();
78
+ return () => {
79
+ subscription.remove();
80
+ FpsCounterModule.stopMonitoring();
81
+ };
82
+ }, []);
83
+ return frameData;
84
+ }
85
+ var defaultStyles = StyleSheet.create({
86
+ container: {
87
+ position: "absolute",
88
+ top: 50,
89
+ left: 8,
90
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
91
+ paddingHorizontal: 10,
92
+ paddingVertical: 6,
93
+ borderRadius: 6,
94
+ minWidth: 80
95
+ },
96
+ row: {
97
+ flexDirection: "row",
98
+ justifyContent: "space-between",
99
+ alignItems: "center",
100
+ marginVertical: 1
101
+ },
102
+ label: {
103
+ color: "rgba(255, 255, 255, 0.7)",
104
+ fontSize: 10,
105
+ fontWeight: "500",
106
+ marginRight: 8
107
+ },
108
+ value: {
109
+ color: "white",
110
+ fontSize: 12,
111
+ fontWeight: "700",
112
+ fontVariant: ["tabular-nums"],
113
+ minWidth: 45,
114
+ textAlign: "right"
115
+ },
116
+ divider: {
117
+ height: 1,
118
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
119
+ marginVertical: 4
120
+ },
121
+ sectionLabel: {
122
+ color: "rgba(255, 255, 255, 0.5)",
123
+ fontSize: 9,
124
+ fontWeight: "600",
125
+ textTransform: "uppercase",
126
+ letterSpacing: 0.5,
127
+ marginBottom: 2
128
+ },
129
+ unavailable: {
130
+ color: "rgba(255, 255, 255, 0.4)",
131
+ fontSize: 9,
132
+ fontStyle: "italic"
133
+ }
134
+ });
135
+ var FpsCounter = ({
136
+ visible = true,
137
+ showJsFps = true,
138
+ showUiFps = true,
139
+ showAverage = true,
140
+ averageDecimalPlaces = 1,
141
+ style
142
+ }) => {
143
+ const { fps: jsFps, average: jsAverage } = useFPSMetric();
144
+ const { uiFps, uiAverage, isAvailable: isUiFpsAvailable } = useUIFPSMetric();
145
+ if (!visible) return null;
146
+ const shouldShowUiFps = showUiFps && isUiFpsAvailable;
147
+ const shouldShowUiFpsUnavailable = showUiFps && !isUiFpsAvailable;
148
+ return /* @__PURE__ */ jsxs(
149
+ View,
150
+ {
151
+ pointerEvents: "none",
152
+ style: [defaultStyles.container, style?.container],
153
+ children: [
154
+ shouldShowUiFps && /* @__PURE__ */ jsxs(Fragment, { children: [
155
+ /* @__PURE__ */ jsx(Text, { style: defaultStyles.sectionLabel, children: "UI Thread" }),
156
+ /* @__PURE__ */ jsxs(View, { style: defaultStyles.row, children: [
157
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.label, style?.labelText], children: "FPS" }),
158
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.value, style?.valueText], children: uiFps })
159
+ ] }),
160
+ showAverage && /* @__PURE__ */ jsxs(View, { style: defaultStyles.row, children: [
161
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.label, style?.labelText], children: "Avg" }),
162
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.value, style?.valueText], children: uiAverage.toFixed(averageDecimalPlaces) })
163
+ ] })
164
+ ] }),
165
+ shouldShowUiFpsUnavailable && /* @__PURE__ */ jsxs(Fragment, { children: [
166
+ /* @__PURE__ */ jsx(Text, { style: defaultStyles.sectionLabel, children: "UI Thread" }),
167
+ /* @__PURE__ */ jsx(Text, { style: defaultStyles.unavailable, children: "Native module not linked" })
168
+ ] }),
169
+ (shouldShowUiFps || shouldShowUiFpsUnavailable) && showJsFps && /* @__PURE__ */ jsx(View, { style: defaultStyles.divider }),
170
+ showJsFps && /* @__PURE__ */ jsxs(Fragment, { children: [
171
+ /* @__PURE__ */ jsx(Text, { style: defaultStyles.sectionLabel, children: "JS Thread" }),
172
+ /* @__PURE__ */ jsxs(View, { style: defaultStyles.row, children: [
173
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.label, style?.labelText], children: "FPS" }),
174
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.value, style?.valueText], children: jsFps })
175
+ ] }),
176
+ showAverage && /* @__PURE__ */ jsxs(View, { style: defaultStyles.row, children: [
177
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.label, style?.labelText], children: "Avg" }),
178
+ /* @__PURE__ */ jsx(Text, { style: [defaultStyles.value, style?.valueText], children: jsAverage.toFixed(averageDecimalPlaces) })
179
+ ] })
180
+ ] })
181
+ ]
182
+ }
183
+ );
184
+ };
185
+
186
+ export { FpsCounter, useFPSMetric, useUIFPSMetric };
187
+ //# sourceMappingURL=index.mjs.map
188
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useFPSMetric.ts","../src/useUIFPSMetric.ts","../src/FpsCounter.tsx"],"names":["useState","useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useEffect, useState } from 'react';\n\ntype FrameData = {\n fps: number;\n lastStamp: number;\n framesCount: number;\n average: number;\n totalCount: number;\n};\n\nexport type FPS = {\n average: FrameData['average'];\n fps: FrameData['fps'];\n};\n\n/**\n * A custom hook that tracks frames per second (FPS) in React Native.\n * Uses requestAnimationFrame to count frames and updates at most once per second.\n *\n * @returns An object containing the current FPS and rolling average FPS\n *\n * @example\n * ```tsx\n * const { fps, average } = useFPSMetric();\n * console.log(`Current: ${fps} FPS, Average: ${average.toFixed(2)} FPS`);\n * ```\n */\nexport function useFPSMetric(): FPS {\n const [frameState, setFrameState] = useState<FrameData>({\n fps: 0,\n lastStamp: Date.now(),\n framesCount: 0,\n average: 0,\n totalCount: 0,\n });\n\n useEffect(() => {\n // NOTE: timeout is here because requestAnimationFrame is deferred\n // and to prevent setStates when unmounted\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n requestAnimationFrame((): void => {\n timeout = setTimeout((): void => {\n const currentStamp = Date.now();\n const shouldSetState = currentStamp - frameState.lastStamp > 1000;\n const newFramesCount = frameState.framesCount + 1;\n\n // Updates FPS at most once per second\n if (shouldSetState) {\n const newValue = frameState.framesCount;\n const totalCount = frameState.totalCount + 1;\n\n // Math.min is used because values over 60 aren't really important\n // Mean FPS is calculated incrementally instead of storing all values\n const newMean = Math.min(\n frameState.average + (newValue - frameState.average) / totalCount,\n 60\n );\n\n setFrameState({\n fps: frameState.framesCount,\n lastStamp: currentStamp,\n framesCount: 0,\n average: newMean,\n totalCount,\n });\n } else {\n setFrameState({\n ...frameState,\n framesCount: newFramesCount,\n });\n }\n }, 0);\n });\n\n return () => {\n if (timeout) clearTimeout(timeout);\n };\n }, [frameState]);\n\n return { average: frameState.average, fps: frameState.fps };\n}\n","import { useEffect, useState, useRef } from 'react';\nimport { NativeModules, NativeEventEmitter, Platform } from 'react-native';\n\nconst FpsCounterModule = NativeModules.FpsCounter;\n\ntype UIFrameData = {\n uiFps: number;\n uiAverage: number;\n isAvailable: boolean;\n};\n\nexport type UIFPS = {\n uiFps: number;\n uiAverage: number;\n /** Whether the native module is available */\n isAvailable: boolean;\n};\n\n/**\n * A custom hook that tracks native UI thread frames per second.\n * Uses CADisplayLink on iOS and Choreographer on Android.\n *\n * @returns An object containing the current UI FPS, rolling average, and availability status\n *\n * @example\n * ```tsx\n * const { uiFps, uiAverage, isAvailable } = useUIFPSMetric();\n * if (isAvailable) {\n * console.log(`UI Thread: ${uiFps} FPS`);\n * }\n * ```\n */\nexport function useUIFPSMetric(): UIFPS {\n const [frameData, setFrameData] = useState<UIFrameData>({\n uiFps: 0,\n uiAverage: 0,\n isAvailable: !!FpsCounterModule,\n });\n const eventEmitterRef = useRef<NativeEventEmitter | null>(null);\n\n useEffect(() => {\n // Handle case where native module is not available\n if (!FpsCounterModule) {\n if (__DEV__) {\n console.warn(\n '[react-native-fps-counter] Native module not found.\\n' +\n 'Make sure you have run:\\n' +\n ' - iOS: cd ios && pod install\\n' +\n ' - Android: Rebuild the app (npx react-native run-android)\\n' +\n 'If using Expo, you need to create a development build.'\n );\n }\n return;\n }\n\n // Create event emitter\n eventEmitterRef.current = new NativeEventEmitter(FpsCounterModule);\n\n // Subscribe to FPS updates\n const subscription = eventEmitterRef.current.addListener(\n 'onUIFpsUpdate',\n (data: { fps: number; average: number }) => {\n setFrameData({\n uiFps: Math.round(data.fps),\n uiAverage: data.average,\n isAvailable: true,\n });\n }\n );\n\n // Start monitoring\n FpsCounterModule.startMonitoring();\n\n return () => {\n subscription.remove();\n FpsCounterModule.stopMonitoring();\n };\n }, []);\n\n return frameData;\n}\n","import React from 'react';\nimport { StyleSheet, Text, View, ViewStyle, TextStyle } from 'react-native';\nimport { useFPSMetric } from './useFPSMetric';\nimport { useUIFPSMetric } from './useUIFPSMetric';\n\nexport interface FpsCounterStyles {\n container?: ViewStyle;\n text?: TextStyle;\n labelText?: TextStyle;\n valueText?: TextStyle;\n jsSection?: ViewStyle;\n uiSection?: ViewStyle;\n}\n\nexport interface FpsCounterProps {\n /**\n * Whether the FPS counter is visible\n * @default true\n */\n visible?: boolean;\n\n /**\n * Whether to show JS thread FPS\n * @default true\n */\n showJsFps?: boolean;\n\n /**\n * Whether to show UI thread FPS (requires native module)\n * @default true\n */\n showUiFps?: boolean;\n\n /**\n * Whether to show average FPS values\n * @default true\n */\n showAverage?: boolean;\n\n /**\n * Number of decimal places for average values\n * @default 1\n */\n averageDecimalPlaces?: number;\n\n /**\n * Custom styles to override defaults\n */\n style?: FpsCounterStyles;\n}\n\nconst defaultStyles = StyleSheet.create({\n container: {\n position: 'absolute',\n top: 50,\n left: 8,\n backgroundColor: 'rgba(0, 0, 0, 0.75)',\n paddingHorizontal: 10,\n paddingVertical: 6,\n borderRadius: 6,\n minWidth: 80,\n },\n row: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginVertical: 1,\n },\n label: {\n color: 'rgba(255, 255, 255, 0.7)',\n fontSize: 10,\n fontWeight: '500',\n marginRight: 8,\n },\n value: {\n color: 'white',\n fontSize: 12,\n fontWeight: '700',\n fontVariant: ['tabular-nums'],\n minWidth: 45,\n textAlign: 'right',\n },\n divider: {\n height: 1,\n backgroundColor: 'rgba(255, 255, 255, 0.2)',\n marginVertical: 4,\n },\n sectionLabel: {\n color: 'rgba(255, 255, 255, 0.5)',\n fontSize: 9,\n fontWeight: '600',\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n marginBottom: 2,\n },\n unavailable: {\n color: 'rgba(255, 255, 255, 0.4)',\n fontSize: 9,\n fontStyle: 'italic',\n },\n});\n\n/**\n * A React Native component that displays both JS thread and native UI thread FPS.\n * Should be placed at the root of your app for accurate measurements.\n *\n * @example\n * ```tsx\n * import { FpsCounter } from 'react-native-fps-counter';\n *\n * export default function App() {\n * return (\n * <View style={{ flex: 1 }}>\n * <YourAppContent />\n * <FpsCounter visible={__DEV__} />\n * </View>\n * );\n * }\n * ```\n */\nexport const FpsCounter: React.FC<FpsCounterProps> = ({\n visible = true,\n showJsFps = true,\n showUiFps = true,\n showAverage = true,\n averageDecimalPlaces = 1,\n style,\n}) => {\n const { fps: jsFps, average: jsAverage } = useFPSMetric();\n const { uiFps, uiAverage, isAvailable: isUiFpsAvailable } = useUIFPSMetric();\n\n if (!visible) return null;\n\n const shouldShowUiFps = showUiFps && isUiFpsAvailable;\n const shouldShowUiFpsUnavailable = showUiFps && !isUiFpsAvailable;\n\n return (\n <View\n pointerEvents=\"none\"\n style={[defaultStyles.container, style?.container]}\n >\n {shouldShowUiFps && (\n <>\n <Text style={defaultStyles.sectionLabel}>UI Thread</Text>\n <View style={defaultStyles.row}>\n <Text style={[defaultStyles.label, style?.labelText]}>FPS</Text>\n <Text style={[defaultStyles.value, style?.valueText]}>{uiFps}</Text>\n </View>\n {showAverage && (\n <View style={defaultStyles.row}>\n <Text style={[defaultStyles.label, style?.labelText]}>Avg</Text>\n <Text style={[defaultStyles.value, style?.valueText]}>\n {uiAverage.toFixed(averageDecimalPlaces)}\n </Text>\n </View>\n )}\n </>\n )}\n\n {shouldShowUiFpsUnavailable && (\n <>\n <Text style={defaultStyles.sectionLabel}>UI Thread</Text>\n <Text style={defaultStyles.unavailable}>Native module not linked</Text>\n </>\n )}\n\n {(shouldShowUiFps || shouldShowUiFpsUnavailable) && showJsFps && (\n <View style={defaultStyles.divider} />\n )}\n\n {showJsFps && (\n <>\n <Text style={defaultStyles.sectionLabel}>JS Thread</Text>\n <View style={defaultStyles.row}>\n <Text style={[defaultStyles.label, style?.labelText]}>FPS</Text>\n <Text style={[defaultStyles.value, style?.valueText]}>{jsFps}</Text>\n </View>\n {showAverage && (\n <View style={defaultStyles.row}>\n <Text style={[defaultStyles.label, style?.labelText]}>Avg</Text>\n <Text style={[defaultStyles.value, style?.valueText]}>\n {jsAverage.toFixed(averageDecimalPlaces)}\n </Text>\n </View>\n )}\n </>\n )}\n </View>\n );\n};\n"]}
@@ -0,0 +1,12 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ #ifdef RCT_NEW_ARCH_ENABLED
5
+ #import <RNFpsCounterSpec/RNFpsCounterSpec.h>
6
+ #endif
7
+
8
+ @interface FpsCounterModule : RCTEventEmitter <RCTBridgeModule>
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+ , NativeFpsCounterSpec
11
+ #endif
12
+ @end
@@ -0,0 +1,123 @@
1
+ #import "FpsCounterModule.h"
2
+ #import <QuartzCore/CADisplayLink.h>
3
+ #import <React/RCTLog.h>
4
+
5
+ @implementation FpsCounterModule {
6
+ CADisplayLink *_displayLink;
7
+ CFTimeInterval _lastTimestamp;
8
+ NSInteger _frameCount;
9
+ double _fps;
10
+ double _averageFps;
11
+ NSInteger _totalSamples;
12
+ BOOL _isMonitoring;
13
+ BOOL _hasListeners;
14
+ }
15
+
16
+ RCT_EXPORT_MODULE(FpsCounter)
17
+
18
+ + (BOOL)requiresMainQueueSetup {
19
+ return YES;
20
+ }
21
+
22
+ - (instancetype)init {
23
+ if (self = [super init]) {
24
+ _frameCount = 0;
25
+ _lastTimestamp = 0;
26
+ _fps = 0;
27
+ _averageFps = 0;
28
+ _totalSamples = 0;
29
+ _isMonitoring = NO;
30
+ _hasListeners = NO;
31
+ }
32
+ return self;
33
+ }
34
+
35
+ - (NSArray<NSString *> *)supportedEvents {
36
+ return @[ @"onUIFpsUpdate" ];
37
+ }
38
+
39
+ - (void)startObserving {
40
+ _hasListeners = YES;
41
+ }
42
+
43
+ - (void)stopObserving {
44
+ _hasListeners = NO;
45
+ }
46
+
47
+ RCT_EXPORT_METHOD(startMonitoring) {
48
+ if (_isMonitoring) {
49
+ return;
50
+ }
51
+
52
+ _isMonitoring = YES;
53
+ _frameCount = 0;
54
+ _lastTimestamp = 0;
55
+ _fps = 0;
56
+ _averageFps = 0;
57
+ _totalSamples = 0;
58
+
59
+ dispatch_async(dispatch_get_main_queue(), ^{
60
+ self->_displayLink =
61
+ [CADisplayLink displayLinkWithTarget:self
62
+ selector:@selector(onDisplayLink:)];
63
+ [self->_displayLink addToRunLoop:[NSRunLoop mainRunLoop]
64
+ forMode:NSRunLoopCommonModes];
65
+ });
66
+ }
67
+
68
+ RCT_EXPORT_METHOD(stopMonitoring) {
69
+ if (!_isMonitoring) {
70
+ return;
71
+ }
72
+
73
+ _isMonitoring = NO;
74
+
75
+ dispatch_async(dispatch_get_main_queue(), ^{
76
+ [self->_displayLink invalidate];
77
+ self->_displayLink = nil;
78
+ });
79
+ }
80
+
81
+ - (void)onDisplayLink:(CADisplayLink *)displayLink {
82
+ if (_lastTimestamp == 0) {
83
+ _lastTimestamp = displayLink.timestamp;
84
+ return;
85
+ }
86
+
87
+ _frameCount++;
88
+
89
+ CFTimeInterval elapsed = displayLink.timestamp - _lastTimestamp;
90
+
91
+ // Update FPS approximately once per second
92
+ if (elapsed >= 1.0) {
93
+ _fps = (double)_frameCount / elapsed;
94
+ _totalSamples++;
95
+
96
+ // Calculate rolling average (capped at 120 FPS for high refresh rate
97
+ // displays)
98
+ _averageFps =
99
+ MIN(_averageFps + (_fps - _averageFps) / _totalSamples, 120.0);
100
+
101
+ _frameCount = 0;
102
+ _lastTimestamp = displayLink.timestamp;
103
+
104
+ if (_hasListeners) {
105
+ [self sendEventWithName:@"onUIFpsUpdate"
106
+ body:@{@"fps" : @(_fps), @"average" : @(_averageFps)}];
107
+ }
108
+ }
109
+ }
110
+
111
+ - (void)invalidate {
112
+ [self stopMonitoring];
113
+ [super invalidate];
114
+ }
115
+
116
+ #ifdef RCT_NEW_ARCH_ENABLED
117
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
118
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
119
+ return std::make_shared<facebook::react::NativeFpsCounterSpecJSI>(params);
120
+ }
121
+ #endif
122
+
123
+ @end
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "RNFpsCounter"
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.homepage = package['homepage']
10
+ s.license = package['license']
11
+ s.authors = package['author']
12
+ s.platforms = { :ios => "12.4" }
13
+ s.source = { :git => package['repository']['url'], :tag => "v#{s.version}" }
14
+ s.source_files = "*.{h,m,mm}"
15
+
16
+ if respond_to?(:install_modules_dependencies, true)
17
+ install_modules_dependencies(s)
18
+ else
19
+ s.dependency "React-Core"
20
+ end
21
+ end
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@vyshnav18/react-native-fps-counter",
3
+ "version": "1.0.0",
4
+ "description": "High-performance FPS counter for React Native with native UI thread and JS thread tracking. Uses Turbo Modules for maximum performance.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "react-native": "src/index.ts",
16
+ "source": "src/index.ts",
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "ios",
21
+ "android",
22
+ "react-native.config.js",
23
+ "!**/__tests__"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "prepublishOnly": "npm run build",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "keywords": [
32
+ "react-native",
33
+ "fps",
34
+ "performance",
35
+ "counter",
36
+ "frames-per-second",
37
+ "debugging",
38
+ "profiling",
39
+ "turbo-module",
40
+ "jsi",
41
+ "ui-thread"
42
+ ],
43
+ "author": "",
44
+ "license": "MIT",
45
+ "peerDependencies": {
46
+ "react": ">=16.8.0",
47
+ "react-native": ">=0.60.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^18.2.0",
51
+ "@types/react-native": "^0.72.0",
52
+ "react": "^18.2.0",
53
+ "react-native": "^0.72.0",
54
+ "tsup": "^8.0.0",
55
+ "typescript": "^5.3.0"
56
+ },
57
+ "codegenConfig": {
58
+ "name": "RNFpsCounterSpec",
59
+ "type": "modules",
60
+ "jsSrcsDir": "src/specs",
61
+ "android": {
62
+ "javaPackageName": "com.rnfpscounter"
63
+ }
64
+ },
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "https://github.com/your-username/react-native-fps-counter.git"
68
+ },
69
+ "bugs": {
70
+ "url": "https://github.com/your-username/react-native-fps-counter/issues"
71
+ },
72
+ "homepage": "https://github.com/your-username/react-native-fps-counter#readme"
73
+ }
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ ios: {
5
+ podspecPath: './ios/RNFpsCounter.podspec',
6
+ },
7
+ android: {
8
+ sourceDir: './android',
9
+ packageImportPath: 'import com.rnfpscounter.FpsCounterPackage;',
10
+ packageInstance: 'new FpsCounterPackage()',
11
+ },
12
+ },
13
+ },
14
+ };
@@ -0,0 +1,190 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View, ViewStyle, TextStyle } from 'react-native';
3
+ import { useFPSMetric } from './useFPSMetric';
4
+ import { useUIFPSMetric } from './useUIFPSMetric';
5
+
6
+ export interface FpsCounterStyles {
7
+ container?: ViewStyle;
8
+ text?: TextStyle;
9
+ labelText?: TextStyle;
10
+ valueText?: TextStyle;
11
+ jsSection?: ViewStyle;
12
+ uiSection?: ViewStyle;
13
+ }
14
+
15
+ export interface FpsCounterProps {
16
+ /**
17
+ * Whether the FPS counter is visible
18
+ * @default true
19
+ */
20
+ visible?: boolean;
21
+
22
+ /**
23
+ * Whether to show JS thread FPS
24
+ * @default true
25
+ */
26
+ showJsFps?: boolean;
27
+
28
+ /**
29
+ * Whether to show UI thread FPS (requires native module)
30
+ * @default true
31
+ */
32
+ showUiFps?: boolean;
33
+
34
+ /**
35
+ * Whether to show average FPS values
36
+ * @default true
37
+ */
38
+ showAverage?: boolean;
39
+
40
+ /**
41
+ * Number of decimal places for average values
42
+ * @default 1
43
+ */
44
+ averageDecimalPlaces?: number;
45
+
46
+ /**
47
+ * Custom styles to override defaults
48
+ */
49
+ style?: FpsCounterStyles;
50
+ }
51
+
52
+ const defaultStyles = StyleSheet.create({
53
+ container: {
54
+ position: 'absolute',
55
+ top: 50,
56
+ left: 8,
57
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
58
+ paddingHorizontal: 10,
59
+ paddingVertical: 6,
60
+ borderRadius: 6,
61
+ minWidth: 80,
62
+ },
63
+ row: {
64
+ flexDirection: 'row',
65
+ justifyContent: 'space-between',
66
+ alignItems: 'center',
67
+ marginVertical: 1,
68
+ },
69
+ label: {
70
+ color: 'rgba(255, 255, 255, 0.7)',
71
+ fontSize: 10,
72
+ fontWeight: '500',
73
+ marginRight: 8,
74
+ },
75
+ value: {
76
+ color: 'white',
77
+ fontSize: 12,
78
+ fontWeight: '700',
79
+ fontVariant: ['tabular-nums'],
80
+ minWidth: 45,
81
+ textAlign: 'right',
82
+ },
83
+ divider: {
84
+ height: 1,
85
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
86
+ marginVertical: 4,
87
+ },
88
+ sectionLabel: {
89
+ color: 'rgba(255, 255, 255, 0.5)',
90
+ fontSize: 9,
91
+ fontWeight: '600',
92
+ textTransform: 'uppercase',
93
+ letterSpacing: 0.5,
94
+ marginBottom: 2,
95
+ },
96
+ unavailable: {
97
+ color: 'rgba(255, 255, 255, 0.4)',
98
+ fontSize: 9,
99
+ fontStyle: 'italic',
100
+ },
101
+ });
102
+
103
+ /**
104
+ * A React Native component that displays both JS thread and native UI thread FPS.
105
+ * Should be placed at the root of your app for accurate measurements.
106
+ *
107
+ * @example
108
+ * ```tsx
109
+ * import { FpsCounter } from 'react-native-fps-counter';
110
+ *
111
+ * export default function App() {
112
+ * return (
113
+ * <View style={{ flex: 1 }}>
114
+ * <YourAppContent />
115
+ * <FpsCounter visible={__DEV__} />
116
+ * </View>
117
+ * );
118
+ * }
119
+ * ```
120
+ */
121
+ export const FpsCounter: React.FC<FpsCounterProps> = ({
122
+ visible = true,
123
+ showJsFps = true,
124
+ showUiFps = true,
125
+ showAverage = true,
126
+ averageDecimalPlaces = 1,
127
+ style,
128
+ }) => {
129
+ const { fps: jsFps, average: jsAverage } = useFPSMetric();
130
+ const { uiFps, uiAverage, isAvailable: isUiFpsAvailable } = useUIFPSMetric();
131
+
132
+ if (!visible) return null;
133
+
134
+ const shouldShowUiFps = showUiFps && isUiFpsAvailable;
135
+ const shouldShowUiFpsUnavailable = showUiFps && !isUiFpsAvailable;
136
+
137
+ return (
138
+ <View
139
+ pointerEvents="none"
140
+ style={[defaultStyles.container, style?.container]}
141
+ >
142
+ {shouldShowUiFps && (
143
+ <>
144
+ <Text style={defaultStyles.sectionLabel}>UI Thread</Text>
145
+ <View style={defaultStyles.row}>
146
+ <Text style={[defaultStyles.label, style?.labelText]}>FPS</Text>
147
+ <Text style={[defaultStyles.value, style?.valueText]}>{uiFps}</Text>
148
+ </View>
149
+ {showAverage && (
150
+ <View style={defaultStyles.row}>
151
+ <Text style={[defaultStyles.label, style?.labelText]}>Avg</Text>
152
+ <Text style={[defaultStyles.value, style?.valueText]}>
153
+ {uiAverage.toFixed(averageDecimalPlaces)}
154
+ </Text>
155
+ </View>
156
+ )}
157
+ </>
158
+ )}
159
+
160
+ {shouldShowUiFpsUnavailable && (
161
+ <>
162
+ <Text style={defaultStyles.sectionLabel}>UI Thread</Text>
163
+ <Text style={defaultStyles.unavailable}>Native module not linked</Text>
164
+ </>
165
+ )}
166
+
167
+ {(shouldShowUiFps || shouldShowUiFpsUnavailable) && showJsFps && (
168
+ <View style={defaultStyles.divider} />
169
+ )}
170
+
171
+ {showJsFps && (
172
+ <>
173
+ <Text style={defaultStyles.sectionLabel}>JS Thread</Text>
174
+ <View style={defaultStyles.row}>
175
+ <Text style={[defaultStyles.label, style?.labelText]}>FPS</Text>
176
+ <Text style={[defaultStyles.value, style?.valueText]}>{jsFps}</Text>
177
+ </View>
178
+ {showAverage && (
179
+ <View style={defaultStyles.row}>
180
+ <Text style={[defaultStyles.label, style?.labelText]}>Avg</Text>
181
+ <Text style={[defaultStyles.value, style?.valueText]}>
182
+ {jsAverage.toFixed(averageDecimalPlaces)}
183
+ </Text>
184
+ </View>
185
+ )}
186
+ </>
187
+ )}
188
+ </View>
189
+ );
190
+ };
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Hooks
2
+ export { useFPSMetric } from './useFPSMetric';
3
+ export type { FPS } from './useFPSMetric';
4
+
5
+ export { useUIFPSMetric } from './useUIFPSMetric';
6
+ export type { UIFPS } from './useUIFPSMetric';
7
+
8
+ // Component
9
+ export { FpsCounter } from './FpsCounter';
10
+ export type { FpsCounterProps, FpsCounterStyles } from './FpsCounter';