Skip to content

Commit 938e7ca

Browse files
committed
fixes, add refresh rate getters
1 parent 71221fa commit 938e7ca

9 files changed

Lines changed: 136 additions & 28 deletions

File tree

android/src/main/java/com/margelo/nitro/performancetoolkit/HybridPerformanceToolkit.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,24 @@ class HybridPerformanceToolkit : HybridPerformanceToolkitSpec() {
234234
0
235235
}
236236
}
237+
238+
override fun getDeviceMaxRefreshRate(): Double {
239+
val context = NitroModules.applicationContext as? ReactApplicationContext
240+
return if (context != null) {
241+
com.performancetoolkit.DeviceUtils.getDeviceMaxRefreshRate(context)
242+
} else {
243+
android.util.Log.w("PerformanceToolkit", "ReactApplicationContext not available, returning default 60 Hz")
244+
60.0
245+
}
246+
}
247+
248+
override fun getDeviceCurrentRefreshRate(): Double {
249+
val context = NitroModules.applicationContext as? ReactApplicationContext
250+
return if (context != null) {
251+
com.performancetoolkit.DeviceUtils.getDeviceCurrentRefreshRate(context)
252+
} else {
253+
android.util.Log.w("PerformanceToolkit", "ReactApplicationContext not available, returning default 60 Hz")
254+
60.0
255+
}
256+
}
237257
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.performancetoolkit
2+
3+
import android.content.Context
4+
import android.os.Build
5+
import android.util.Log
6+
import android.view.WindowManager
7+
import com.facebook.react.bridge.ReactApplicationContext
8+
import kotlin.math.roundToInt
9+
10+
object DeviceUtils {
11+
private const val TAG = "PerformanceToolkit"
12+
13+
fun getDeviceMaxRefreshRate(context: ReactApplicationContext): Double {
14+
return try {
15+
val display = getDisplay(context)
16+
17+
// Get maximum refresh rate from supported modes (API 23+)
18+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
19+
display?.supportedModes
20+
?.maxOfOrNull { it.refreshRate }
21+
?.roundToInt()?.toDouble() ?: 60.0
22+
} else {
23+
// Fall back to current refresh rate on older devices
24+
display?.refreshRate?.roundToInt()?.toDouble() ?: 60.0
25+
}
26+
} catch (e: Exception) {
27+
Log.e(TAG, "Error getting max device refresh rate, defaulting to 60", e)
28+
60.0
29+
}
30+
}
31+
32+
fun getDeviceCurrentRefreshRate(context: ReactApplicationContext): Double {
33+
return try {
34+
val display = getDisplay(context)
35+
36+
// Get current refresh rate
37+
display?.refreshRate?.roundToInt()?.toDouble() ?: 60.0
38+
} catch (e: Exception) {
39+
Log.e(TAG, "Error getting current device refresh rate, defaulting to 60", e)
40+
60.0
41+
}
42+
}
43+
44+
private fun getDisplay(context: ReactApplicationContext): android.view.Display? {
45+
// Try to get display from current activity first
46+
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
47+
context.currentActivity?.display
48+
} else {
49+
null
50+
}
51+
52+
// Fall back to WindowManager's default display
53+
return display ?: run {
54+
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
55+
@Suppress("DEPRECATION")
56+
windowManager?.defaultDisplay
57+
}
58+
}
59+
}
60+

android/src/main/java/com/performancetoolkit/PerformanceToolkitTurboModule.kt

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class PerformanceToolkitTurboModule(reactContext: ReactApplicationContext) :
3939
?: throw IllegalStateException("PerformanceToolkit: React Native runtime executor is not available. Please ensure New Architecture is enabled.")
4040

4141
runtimeExecutor = executor
42-
val deviceFps = getDeviceRefreshRate(reactContext)
42+
val deviceFps = DeviceUtils.getDeviceMaxRefreshRate(reactContext)
4343

4444
try {
4545
hybridData = initHybrid(runtimeExecutor, deviceFps)
@@ -48,29 +48,6 @@ class PerformanceToolkitTurboModule(reactContext: ReactApplicationContext) :
4848
throw e
4949
}
5050
}
51-
52-
private fun getDeviceRefreshRate(context: ReactApplicationContext): Double {
53-
return try {
54-
// Try to get display from current activity first
55-
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
56-
context.currentActivity?.display
57-
} else {
58-
null
59-
}
60-
61-
// Fall back to WindowManager's default display
62-
val finalDisplay = display ?: run {
63-
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
64-
@Suppress("DEPRECATION")
65-
windowManager?.defaultDisplay
66-
}
67-
68-
finalDisplay?.refreshRate?.toDouble() ?: 60.0
69-
} catch (e: Exception) {
70-
Log.e(TAG, "Error getting device refresh rate, defaulting to 60", e)
71-
60.0
72-
}
73-
}
7451

7552
override fun invalidate() {
7653
try {

cpp/HybridJsFpsTracking.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class JsFpsTracker : public std::enable_shared_from_this<JsFpsTracker> {
5252

5353
auto self = shared_from_this();
5454
// Get frame interval dynamically based on device refresh rate
55-
const double frameIntervalMs = RuntimeBridgeState::get().getFrameIntervalMs();
55+
// const double frameIntervalMs = RuntimeBridgeState::get().getFrameIntervalMs();
56+
const double frameIntervalMs = 16.666666666666668; // For now fix it to 60 FPS
5657
const auto frameInterval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(
5758
std::chrono::duration<double, std::milli>(frameIntervalMs)
5859
);
@@ -127,7 +128,8 @@ class JsFpsTracker : public std::enable_shared_from_this<JsFpsTracker> {
127128
fps = 0.0;
128129
}
129130

130-
double deviceMaxFps = RuntimeBridgeState::get().getDeviceRefreshRate();
131+
// double deviceMaxFps = RuntimeBridgeState::get().getDeviceRefreshRate();
132+
const double deviceMaxFps = 60.0; // For now fix it to 60 FPS
131133
double cappedFps = std::min(std::round(fps), deviceMaxFps);
132134

133135
// Write to native buffer as Int32 (not on JS thread)

example/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
useCpuUsage,
66
useMemoryUsage,
77
useFpsJs,
8+
getDeviceMaxRefreshRate,
9+
getDeviceCurrentRefreshRate,
810
} from 'react-native-performance-toolkit';
911
import { GestureHandlerRootView } from 'react-native-gesture-handler';
1012
import {
@@ -84,6 +86,15 @@ function App(): React.JSX.Element {
8486
</Text>
8587
</TouchableOpacity>
8688

89+
<View>
90+
<Text style={styles.subtitle}>
91+
Device Max Refresh Rate: {getDeviceMaxRefreshRate()} Hz
92+
</Text>
93+
<Text style={styles.subtitle}>
94+
Device Current Refresh Rate: {getDeviceCurrentRefreshRate()} Hz
95+
</Text>
96+
</View>
97+
8798
<View>
8899
<Text style={styles.subtitle}>JS Thread updated values</Text>
89100
</View>

ios/HybridPerformanceToolkit.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,24 @@ class HybridPerformanceToolkit: HybridPerformanceToolkitSpec {
292292

293293
return Double(info.phys_footprint) / 1_048_576.0
294294
}
295+
296+
func getDeviceMaxRefreshRate() throws -> Double {
297+
return maxDeviceFps
298+
}
299+
300+
func getDeviceCurrentRefreshRate() throws -> Double {
301+
// On iOS, the current refresh rate is the same as max for most cases
302+
// ProMotion devices (120Hz) may throttle down, but CADisplayLink will reflect this
303+
if let displayLink = displayLink {
304+
// preferredFramesPerSecond returns the actual frame rate being used
305+
// 0 means maximum available, so we return maxDeviceFps
306+
if displayLink.preferredFramesPerSecond == 0 {
307+
return maxDeviceFps
308+
}
309+
return Double(displayLink.preferredFramesPerSecond)
310+
}
311+
312+
// Fallback to current screen refresh rate
313+
return Double(UIScreen.main.maximumFramesPerSecond)
314+
}
295315
}

src/hooks/jsThreadHooks.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ const getUiFpsBuffer = () => PerformanceToolkit.getUiFpsBuffer()
88
const getCpuUsageBuffer = () => PerformanceToolkit.getCpuUsageBuffer()
99
const getMemoryUsageBuffer = () => PerformanceToolkit.getMemoryUsageBuffer()
1010

11+
const getValueFromBuffer = (buffer: ArrayBuffer) => {
12+
const view = new DataView(buffer)
13+
return view.getInt32(0, true) // true = littleEndian
14+
}
15+
16+
export const getJsFps = () => getValueFromBuffer(getJsFpsBuffer())
17+
export const getUiFps = () => getValueFromBuffer(getUiFpsBuffer())
18+
export const getCpuUsage = () => getValueFromBuffer(getCpuUsageBuffer())
19+
export const getMemoryUsage = () => getValueFromBuffer(getMemoryUsageBuffer())
20+
1121
const prepareOnChange = (
1222
bufferGetter: () => ArrayBuffer,
1323
intervalMs: number = 1000
@@ -19,8 +29,7 @@ const prepareOnChange = (
1929
console.error(`Failed to get buffer.`)
2030
return
2131
}
22-
const view = new DataView(buffer)
23-
callback(view.getInt32(0, true)) // true = littleEndian
32+
callback(getValueFromBuffer(buffer))
2433
}, intervalMs)
2534

2635
return () => {

src/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import './specs/TurboPerformanceToolkit'
22

33
import React from 'react'
44
import { UIThreadReanimatedCounter } from './components/UIThreadReanimatedCounter'
5+
import { PerformanceToolkit } from './hybrids'
56

67
export {
78
BoxedJsFpsTracking,
@@ -25,4 +26,10 @@ export const MemoryUsageCounter = () => {
2526
return <UIThreadReanimatedCounter label="RAM" type="memory" />
2627
}
2728

29+
export const getDeviceMaxRefreshRate = () =>
30+
PerformanceToolkit.getDeviceMaxRefreshRate()
31+
32+
export const getDeviceCurrentRefreshRate = () =>
33+
PerformanceToolkit.getDeviceCurrentRefreshRate()
34+
2835
export * from './hooks/jsThreadHooks'

src/specs/performance-toolkit.nitro.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ export interface PerformanceToolkit
55
getUiFpsBuffer(): ArrayBuffer
66
getCpuUsageBuffer(): ArrayBuffer
77
getMemoryUsageBuffer(): ArrayBuffer
8+
getDeviceMaxRefreshRate(): number
9+
getDeviceCurrentRefreshRate(): number
810
}

0 commit comments

Comments
 (0)