Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class DdSdkImplementation(
private val reactContext: ReactApplicationContext,
private val datadog: DatadogWrapper = DatadogSDKWrapper(),
private val ddTelemetry: DdTelemetry = DdTelemetry(),
private val uiThreadExecutor: UiThreadExecutor = ReactUiThreadExecutor()
private val uiThreadExecutor: UiThreadExecutor = ReactUiThreadExecutor(),
private val jsThreadExecutor: JsThreadExecutor = ReactJsThreadExecutor(reactContext)
) {
internal val appContext: Context = reactContext.applicationContext
internal val initialized = AtomicBoolean(false)
Expand Down Expand Up @@ -327,10 +328,8 @@ class DdSdkImplementation(
ddSdkConfiguration: DdSdkConfiguration
): FrameRateProvider? {
val frameTimeCallback = buildFrameTimeCallback(ddSdkConfiguration) ?: return null
val frameRateProvider = FrameRateProvider(frameTimeCallback, uiThreadExecutor)
reactContext.runOnJSQueueThread {
frameRateProvider.start()
}
val frameRateProvider = FrameRateProvider(frameTimeCallback, jsThreadExecutor)
frameRateProvider.start()

return frameRateProvider
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import android.view.Choreographer

internal class FrameRateProvider(
reactFrameRateCallback: ((Double) -> Unit),
uiThreadExecutor: UiThreadExecutor
jsThreadExecutor: JsThreadExecutor
) {
private val frameCallback: FpsFrameCallback = FpsFrameCallback(
reactFrameRateCallback,
uiThreadExecutor
jsThreadExecutor
)

fun start() {
Expand All @@ -29,7 +29,7 @@ internal class FrameRateProvider(

internal class FpsFrameCallback(
private val reactFrameRateCallback: ((Double) -> Unit),
private val uiThreadExecutor: UiThreadExecutor
private val jsThreadExecutor: JsThreadExecutor
) : Choreographer.FrameCallback {

private var choreographer: Choreographer? = null
Expand All @@ -43,16 +43,24 @@ internal class FpsFrameCallback(
choreographer?.postFrameCallback(this)
}

@Suppress("SwallowedException")
fun start() {
uiThreadExecutor.runOnUiThread {
choreographer = Choreographer.getInstance()
choreographer?.postFrameCallback(this@FpsFrameCallback)
// Choreographer is thread-local: we register on the JS thread so frame callbacks
// measure JS frame timings, matching the iOS CADisplayLink-on-JS-RunLoop approach.
jsThreadExecutor.runOnJsThread {
try {
val instance = Choreographer.getInstance()
instance.removeFrameCallback(this@FpsFrameCallback)
choreographer = instance
instance.postFrameCallback(this@FpsFrameCallback)
Comment on lines +50 to +55
} catch (e: IllegalStateException) {
// Choreographer requires a Looper; guard defensively in case the JS thread lacks one.
}
}
}

fun stop() {
uiThreadExecutor.runOnUiThread {
choreographer = Choreographer.getInstance()
jsThreadExecutor.runOnJsThread {
choreographer?.removeFrameCallback(this@FpsFrameCallback)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative

import com.facebook.react.bridge.ReactApplicationContext

/**
* Simple JS Thread Executor. By default it is based on [ReactApplicationContext.runOnJSQueueThread].
*/
interface JsThreadExecutor {
/**
* Runs the given runnable on the JS Thread.
*/
fun runOnJsThread(runnable: Runnable)
}

internal class ReactJsThreadExecutor(
private val reactContext: ReactApplicationContext
) : JsThreadExecutor {
override fun runOnJsThread(runnable: Runnable) {
reactContext.runOnJSQueueThread(runnable)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.datadog.android.trace.TraceConfiguration
import com.datadog.android.trace.TracingHeaderType
import com.datadog.tools.unit.GenericAssert.Companion.assertThat
import com.datadog.tools.unit.MockRumMonitor
import com.datadog.tools.unit.TestJsThreadExecutor
import com.datadog.tools.unit.TestUiThreadExecutor
import com.datadog.tools.unit.forge.BaseConfigurator
import com.datadog.tools.unit.setStaticValue
Expand Down Expand Up @@ -165,15 +166,12 @@ internal class DdSdkTest {
0
)
) doReturn mockPackageInfo
whenever(mockReactContext.runOnJSQueueThread(any())).thenAnswer { answer ->
answer.getArgument<Runnable>(0).run()
true
}
testedBridgeSdk = DdSdkImplementation(
mockReactContext,
mockDatadog,
mockDdTelemetry,
TestUiThreadExecutor()
TestUiThreadExecutor(),
TestJsThreadExecutor()
)

DatadogSDKWrapperStorage.onInitializedListeners.clear()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.tools.unit

import com.datadog.reactnative.JsThreadExecutor

internal class TestJsThreadExecutor : JsThreadExecutor {
override fun runOnJsThread(runnable: Runnable) {
// Run immediately in the same thread for tests
runnable.run()
}
}
Loading