@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
@@ -0,0 +1,64 @@
1
+ buildscript {
2
+ ext.safeExtGet = {prop, fallback ->
3
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4
+ }
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+ dependencies {
10
+ classpath("com.android.tools.build:gradle:7.4.2")
11
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.8.22')}")
12
+ }
13
+ }
14
+
15
+ apply plugin: 'com.android.library'
16
+ apply plugin: 'kotlin-android'
17
+
18
+ def isNewArchitectureEnabled() {
19
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
20
+ }
21
+
22
+ android {
23
+ namespace "com.rnfpscounter"
24
+ compileSdkVersion safeExtGet('compileSdkVersion', 34)
25
+
26
+ defaultConfig {
27
+ minSdkVersion safeExtGet('minSdkVersion', 21)
28
+ targetSdkVersion safeExtGet('targetSdkVersion', 34)
29
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
30
+ }
31
+
32
+ buildFeatures {
33
+ buildConfig true
34
+ }
35
+
36
+ sourceSets {
37
+ main {
38
+ if (isNewArchitectureEnabled()) {
39
+ java.srcDirs += ['src/newarch']
40
+ } else {
41
+ java.srcDirs += ['src/oldarch']
42
+ }
43
+ }
44
+ }
45
+
46
+ compileOptions {
47
+ sourceCompatibility JavaVersion.VERSION_1_8
48
+ targetCompatibility JavaVersion.VERSION_1_8
49
+ }
50
+
51
+ kotlinOptions {
52
+ jvmTarget = '1.8'
53
+ }
54
+ }
55
+
56
+ repositories {
57
+ google()
58
+ mavenCentral()
59
+ }
60
+
61
+ dependencies {
62
+ implementation "com.facebook.react:react-android"
63
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.8.22')}"
64
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.rnfpscounter">
3
+ </manifest>
@@ -0,0 +1,120 @@
1
+ package com.rnfpscounter
2
+
3
+ import android.view.Choreographer
4
+ import com.facebook.react.bridge.*
5
+ import com.facebook.react.modules.core.DeviceEventManagerModule
6
+
7
+ class FpsCounterModule(reactContext: ReactApplicationContext) :
8
+ ReactContextBaseJavaModule(reactContext),
9
+ LifecycleEventListener {
10
+
11
+ private var choreographerCallback: Choreographer.FrameCallback? = null
12
+ private var lastFrameTimeNanos: Long = 0
13
+ private var frameCount: Int = 0
14
+ private var fps: Double = 0.0
15
+ private var averageFps: Double = 0.0
16
+ private var totalSamples: Int = 0
17
+ private var isMonitoring: Boolean = false
18
+ private var listenerCount: Int = 0
19
+
20
+ override fun getName(): String = NAME
21
+
22
+ init {
23
+ reactContext.addLifecycleEventListener(this)
24
+ }
25
+
26
+ @ReactMethod
27
+ fun startMonitoring() {
28
+ if (isMonitoring) return
29
+
30
+ isMonitoring = true
31
+ frameCount = 0
32
+ lastFrameTimeNanos = 0
33
+ fps = 0.0
34
+ averageFps = 0.0
35
+ totalSamples = 0
36
+
37
+ choreographerCallback = object : Choreographer.FrameCallback {
38
+ override fun doFrame(frameTimeNanos: Long) {
39
+ if (!isMonitoring) return
40
+
41
+ if (lastFrameTimeNanos == 0L) {
42
+ lastFrameTimeNanos = frameTimeNanos
43
+ Choreographer.getInstance().postFrameCallback(this)
44
+ return
45
+ }
46
+
47
+ frameCount++
48
+
49
+ val elapsedNanos = frameTimeNanos - lastFrameTimeNanos
50
+ val elapsedSeconds = elapsedNanos / 1_000_000_000.0
51
+
52
+ // Update FPS approximately once per second
53
+ if (elapsedSeconds >= 1.0) {
54
+ fps = frameCount / elapsedSeconds
55
+ totalSamples++
56
+
57
+ // Calculate rolling average (capped at 120 FPS for high refresh rate displays)
58
+ averageFps = minOf(averageFps + (fps - averageFps) / totalSamples, 120.0)
59
+
60
+ frameCount = 0
61
+ lastFrameTimeNanos = frameTimeNanos
62
+
63
+ if (listenerCount > 0) {
64
+ sendEvent(fps, averageFps)
65
+ }
66
+ }
67
+
68
+ Choreographer.getInstance().postFrameCallback(this)
69
+ }
70
+ }
71
+
72
+ Choreographer.getInstance().postFrameCallback(choreographerCallback!!)
73
+ }
74
+
75
+ @ReactMethod
76
+ fun stopMonitoring() {
77
+ if (!isMonitoring) return
78
+
79
+ isMonitoring = false
80
+ choreographerCallback?.let {
81
+ Choreographer.getInstance().removeFrameCallback(it)
82
+ }
83
+ choreographerCallback = null
84
+ }
85
+
86
+ @ReactMethod
87
+ fun addListener(eventName: String) {
88
+ listenerCount++
89
+ }
90
+
91
+ @ReactMethod
92
+ fun removeListeners(count: Int) {
93
+ listenerCount = maxOf(listenerCount - count, 0)
94
+ }
95
+
96
+ private fun sendEvent(fps: Double, average: Double) {
97
+ reactApplicationContext
98
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
99
+ .emit("onUIFpsUpdate", Arguments.createMap().apply {
100
+ putDouble("fps", fps)
101
+ putDouble("average", average)
102
+ })
103
+ }
104
+
105
+ override fun onHostResume() {}
106
+ override fun onHostPause() {}
107
+
108
+ override fun onHostDestroy() {
109
+ stopMonitoring()
110
+ }
111
+
112
+ override fun invalidate() {
113
+ stopMonitoring()
114
+ super.invalidate()
115
+ }
116
+
117
+ companion object {
118
+ const val NAME = "FpsCounter"
119
+ }
120
+ }
@@ -0,0 +1,16 @@
1
+ package com.rnfpscounter
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class FpsCounterPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(FpsCounterModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { ViewStyle, TextStyle } from 'react-native';
3
+
4
+ type FrameData = {
5
+ fps: number;
6
+ lastStamp: number;
7
+ framesCount: number;
8
+ average: number;
9
+ totalCount: number;
10
+ };
11
+ type FPS = {
12
+ average: FrameData['average'];
13
+ fps: FrameData['fps'];
14
+ };
15
+ /**
16
+ * A custom hook that tracks frames per second (FPS) in React Native.
17
+ * Uses requestAnimationFrame to count frames and updates at most once per second.
18
+ *
19
+ * @returns An object containing the current FPS and rolling average FPS
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const { fps, average } = useFPSMetric();
24
+ * console.log(`Current: ${fps} FPS, Average: ${average.toFixed(2)} FPS`);
25
+ * ```
26
+ */
27
+ declare function useFPSMetric(): FPS;
28
+
29
+ type UIFPS = {
30
+ uiFps: number;
31
+ uiAverage: number;
32
+ /** Whether the native module is available */
33
+ isAvailable: boolean;
34
+ };
35
+ /**
36
+ * A custom hook that tracks native UI thread frames per second.
37
+ * Uses CADisplayLink on iOS and Choreographer on Android.
38
+ *
39
+ * @returns An object containing the current UI FPS, rolling average, and availability status
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * const { uiFps, uiAverage, isAvailable } = useUIFPSMetric();
44
+ * if (isAvailable) {
45
+ * console.log(`UI Thread: ${uiFps} FPS`);
46
+ * }
47
+ * ```
48
+ */
49
+ declare function useUIFPSMetric(): UIFPS;
50
+
51
+ interface FpsCounterStyles {
52
+ container?: ViewStyle;
53
+ text?: TextStyle;
54
+ labelText?: TextStyle;
55
+ valueText?: TextStyle;
56
+ jsSection?: ViewStyle;
57
+ uiSection?: ViewStyle;
58
+ }
59
+ interface FpsCounterProps {
60
+ /**
61
+ * Whether the FPS counter is visible
62
+ * @default true
63
+ */
64
+ visible?: boolean;
65
+ /**
66
+ * Whether to show JS thread FPS
67
+ * @default true
68
+ */
69
+ showJsFps?: boolean;
70
+ /**
71
+ * Whether to show UI thread FPS (requires native module)
72
+ * @default true
73
+ */
74
+ showUiFps?: boolean;
75
+ /**
76
+ * Whether to show average FPS values
77
+ * @default true
78
+ */
79
+ showAverage?: boolean;
80
+ /**
81
+ * Number of decimal places for average values
82
+ * @default 1
83
+ */
84
+ averageDecimalPlaces?: number;
85
+ /**
86
+ * Custom styles to override defaults
87
+ */
88
+ style?: FpsCounterStyles;
89
+ }
90
+ /**
91
+ * A React Native component that displays both JS thread and native UI thread FPS.
92
+ * Should be placed at the root of your app for accurate measurements.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * import { FpsCounter } from 'react-native-fps-counter';
97
+ *
98
+ * export default function App() {
99
+ * return (
100
+ * <View style={{ flex: 1 }}>
101
+ * <YourAppContent />
102
+ * <FpsCounter visible={__DEV__} />
103
+ * </View>
104
+ * );
105
+ * }
106
+ * ```
107
+ */
108
+ declare const FpsCounter: React.FC<FpsCounterProps>;
109
+
110
+ export { type FPS, FpsCounter, type FpsCounterProps, type FpsCounterStyles, type UIFPS, useFPSMetric, useUIFPSMetric };
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { ViewStyle, TextStyle } from 'react-native';
3
+
4
+ type FrameData = {
5
+ fps: number;
6
+ lastStamp: number;
7
+ framesCount: number;
8
+ average: number;
9
+ totalCount: number;
10
+ };
11
+ type FPS = {
12
+ average: FrameData['average'];
13
+ fps: FrameData['fps'];
14
+ };
15
+ /**
16
+ * A custom hook that tracks frames per second (FPS) in React Native.
17
+ * Uses requestAnimationFrame to count frames and updates at most once per second.
18
+ *
19
+ * @returns An object containing the current FPS and rolling average FPS
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const { fps, average } = useFPSMetric();
24
+ * console.log(`Current: ${fps} FPS, Average: ${average.toFixed(2)} FPS`);
25
+ * ```
26
+ */
27
+ declare function useFPSMetric(): FPS;
28
+
29
+ type UIFPS = {
30
+ uiFps: number;
31
+ uiAverage: number;
32
+ /** Whether the native module is available */
33
+ isAvailable: boolean;
34
+ };
35
+ /**
36
+ * A custom hook that tracks native UI thread frames per second.
37
+ * Uses CADisplayLink on iOS and Choreographer on Android.
38
+ *
39
+ * @returns An object containing the current UI FPS, rolling average, and availability status
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * const { uiFps, uiAverage, isAvailable } = useUIFPSMetric();
44
+ * if (isAvailable) {
45
+ * console.log(`UI Thread: ${uiFps} FPS`);
46
+ * }
47
+ * ```
48
+ */
49
+ declare function useUIFPSMetric(): UIFPS;
50
+
51
+ interface FpsCounterStyles {
52
+ container?: ViewStyle;
53
+ text?: TextStyle;
54
+ labelText?: TextStyle;
55
+ valueText?: TextStyle;
56
+ jsSection?: ViewStyle;
57
+ uiSection?: ViewStyle;
58
+ }
59
+ interface FpsCounterProps {
60
+ /**
61
+ * Whether the FPS counter is visible
62
+ * @default true
63
+ */
64
+ visible?: boolean;
65
+ /**
66
+ * Whether to show JS thread FPS
67
+ * @default true
68
+ */
69
+ showJsFps?: boolean;
70
+ /**
71
+ * Whether to show UI thread FPS (requires native module)
72
+ * @default true
73
+ */
74
+ showUiFps?: boolean;
75
+ /**
76
+ * Whether to show average FPS values
77
+ * @default true
78
+ */
79
+ showAverage?: boolean;
80
+ /**
81
+ * Number of decimal places for average values
82
+ * @default 1
83
+ */
84
+ averageDecimalPlaces?: number;
85
+ /**
86
+ * Custom styles to override defaults
87
+ */
88
+ style?: FpsCounterStyles;
89
+ }
90
+ /**
91
+ * A React Native component that displays both JS thread and native UI thread FPS.
92
+ * Should be placed at the root of your app for accurate measurements.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * import { FpsCounter } from 'react-native-fps-counter';
97
+ *
98
+ * export default function App() {
99
+ * return (
100
+ * <View style={{ flex: 1 }}>
101
+ * <YourAppContent />
102
+ * <FpsCounter visible={__DEV__} />
103
+ * </View>
104
+ * );
105
+ * }
106
+ * ```
107
+ */
108
+ declare const FpsCounter: React.FC<FpsCounterProps>;
109
+
110
+ export { type FPS, FpsCounter, type FpsCounterProps, type FpsCounterStyles, type UIFPS, useFPSMetric, useUIFPSMetric };
package/dist/index.js ADDED
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactNative = require('react-native');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/useFPSMetric.ts
8
+ function useFPSMetric() {
9
+ const [frameState, setFrameState] = react.useState({
10
+ fps: 0,
11
+ lastStamp: Date.now(),
12
+ framesCount: 0,
13
+ average: 0,
14
+ totalCount: 0
15
+ });
16
+ react.useEffect(() => {
17
+ let timeout = null;
18
+ requestAnimationFrame(() => {
19
+ timeout = setTimeout(() => {
20
+ const currentStamp = Date.now();
21
+ const shouldSetState = currentStamp - frameState.lastStamp > 1e3;
22
+ const newFramesCount = frameState.framesCount + 1;
23
+ if (shouldSetState) {
24
+ const newValue = frameState.framesCount;
25
+ const totalCount = frameState.totalCount + 1;
26
+ const newMean = Math.min(
27
+ frameState.average + (newValue - frameState.average) / totalCount,
28
+ 60
29
+ );
30
+ setFrameState({
31
+ fps: frameState.framesCount,
32
+ lastStamp: currentStamp,
33
+ framesCount: 0,
34
+ average: newMean,
35
+ totalCount
36
+ });
37
+ } else {
38
+ setFrameState({
39
+ ...frameState,
40
+ framesCount: newFramesCount
41
+ });
42
+ }
43
+ }, 0);
44
+ });
45
+ return () => {
46
+ if (timeout) clearTimeout(timeout);
47
+ };
48
+ }, [frameState]);
49
+ return { average: frameState.average, fps: frameState.fps };
50
+ }
51
+ var FpsCounterModule = reactNative.NativeModules.FpsCounter;
52
+ function useUIFPSMetric() {
53
+ const [frameData, setFrameData] = react.useState({
54
+ uiFps: 0,
55
+ uiAverage: 0,
56
+ isAvailable: !!FpsCounterModule
57
+ });
58
+ const eventEmitterRef = react.useRef(null);
59
+ react.useEffect(() => {
60
+ if (!FpsCounterModule) {
61
+ if (__DEV__) {
62
+ console.warn(
63
+ "[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."
64
+ );
65
+ }
66
+ return;
67
+ }
68
+ eventEmitterRef.current = new reactNative.NativeEventEmitter(FpsCounterModule);
69
+ const subscription = eventEmitterRef.current.addListener(
70
+ "onUIFpsUpdate",
71
+ (data) => {
72
+ setFrameData({
73
+ uiFps: Math.round(data.fps),
74
+ uiAverage: data.average,
75
+ isAvailable: true
76
+ });
77
+ }
78
+ );
79
+ FpsCounterModule.startMonitoring();
80
+ return () => {
81
+ subscription.remove();
82
+ FpsCounterModule.stopMonitoring();
83
+ };
84
+ }, []);
85
+ return frameData;
86
+ }
87
+ var defaultStyles = reactNative.StyleSheet.create({
88
+ container: {
89
+ position: "absolute",
90
+ top: 50,
91
+ left: 8,
92
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
93
+ paddingHorizontal: 10,
94
+ paddingVertical: 6,
95
+ borderRadius: 6,
96
+ minWidth: 80
97
+ },
98
+ row: {
99
+ flexDirection: "row",
100
+ justifyContent: "space-between",
101
+ alignItems: "center",
102
+ marginVertical: 1
103
+ },
104
+ label: {
105
+ color: "rgba(255, 255, 255, 0.7)",
106
+ fontSize: 10,
107
+ fontWeight: "500",
108
+ marginRight: 8
109
+ },
110
+ value: {
111
+ color: "white",
112
+ fontSize: 12,
113
+ fontWeight: "700",
114
+ fontVariant: ["tabular-nums"],
115
+ minWidth: 45,
116
+ textAlign: "right"
117
+ },
118
+ divider: {
119
+ height: 1,
120
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
121
+ marginVertical: 4
122
+ },
123
+ sectionLabel: {
124
+ color: "rgba(255, 255, 255, 0.5)",
125
+ fontSize: 9,
126
+ fontWeight: "600",
127
+ textTransform: "uppercase",
128
+ letterSpacing: 0.5,
129
+ marginBottom: 2
130
+ },
131
+ unavailable: {
132
+ color: "rgba(255, 255, 255, 0.4)",
133
+ fontSize: 9,
134
+ fontStyle: "italic"
135
+ }
136
+ });
137
+ var FpsCounter = ({
138
+ visible = true,
139
+ showJsFps = true,
140
+ showUiFps = true,
141
+ showAverage = true,
142
+ averageDecimalPlaces = 1,
143
+ style
144
+ }) => {
145
+ const { fps: jsFps, average: jsAverage } = useFPSMetric();
146
+ const { uiFps, uiAverage, isAvailable: isUiFpsAvailable } = useUIFPSMetric();
147
+ if (!visible) return null;
148
+ const shouldShowUiFps = showUiFps && isUiFpsAvailable;
149
+ const shouldShowUiFpsUnavailable = showUiFps && !isUiFpsAvailable;
150
+ return /* @__PURE__ */ jsxRuntime.jsxs(
151
+ reactNative.View,
152
+ {
153
+ pointerEvents: "none",
154
+ style: [defaultStyles.container, style?.container],
155
+ children: [
156
+ shouldShowUiFps && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
157
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: defaultStyles.sectionLabel, children: "UI Thread" }),
158
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: defaultStyles.row, children: [
159
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.label, style?.labelText], children: "FPS" }),
160
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.value, style?.valueText], children: uiFps })
161
+ ] }),
162
+ showAverage && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: defaultStyles.row, children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.label, style?.labelText], children: "Avg" }),
164
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.value, style?.valueText], children: uiAverage.toFixed(averageDecimalPlaces) })
165
+ ] })
166
+ ] }),
167
+ shouldShowUiFpsUnavailable && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
168
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: defaultStyles.sectionLabel, children: "UI Thread" }),
169
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: defaultStyles.unavailable, children: "Native module not linked" })
170
+ ] }),
171
+ (shouldShowUiFps || shouldShowUiFpsUnavailable) && showJsFps && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: defaultStyles.divider }),
172
+ showJsFps && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: defaultStyles.sectionLabel, children: "JS Thread" }),
174
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: defaultStyles.row, children: [
175
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.label, style?.labelText], children: "FPS" }),
176
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.value, style?.valueText], children: jsFps })
177
+ ] }),
178
+ showAverage && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: defaultStyles.row, children: [
179
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.label, style?.labelText], children: "Avg" }),
180
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [defaultStyles.value, style?.valueText], children: jsAverage.toFixed(averageDecimalPlaces) })
181
+ ] })
182
+ ] })
183
+ ]
184
+ }
185
+ );
186
+ };
187
+
188
+ exports.FpsCounter = FpsCounter;
189
+ exports.useFPSMetric = useFPSMetric;
190
+ exports.useUIFPSMetric = useUIFPSMetric;
191
+ //# sourceMappingURL=index.js.map
192
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useFPSMetric.ts","../src/useUIFPSMetric.ts","../src/FpsCounter.tsx"],"names":["useState","useEffect","NativeModules","useRef","NativeEventEmitter","StyleSheet","jsxs","View","Fragment","jsx","Text"],"mappings":"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","file":"index.js","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"]}