Skip to content

Commit

Permalink
Convert modules/core/ReactChoreographer to Kotlin
Browse files Browse the repository at this point in the history
Differential Revision: D60445731
  • Loading branch information
rshest authored and facebook-github-bot committed Aug 1, 2024
1 parent fdfa0b1 commit 9d383dd
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 209 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.modules.core

import android.view.Choreographer

public open class ChoreographerCompat {

@Deprecated("Use Choreographer.FrameCallback instead")
public abstract class FrameCallback : Choreographer.FrameCallback
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.modules.core

import android.view.Choreographer
import com.facebook.common.logging.FLog
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.ReactConstants
import com.facebook.react.common.annotations.VisibleForTesting
import com.facebook.react.internal.ChoreographerProvider
import java.util.ArrayDeque

/**
* A simple wrapper around Choreographer that allows us to control the order certain callbacks are
* executed within a given frame. The wrapped Choreographer instance will always be the main thread
* one and the API's are safe to use from any thread.
*/
public class ReactChoreographer private constructor(choreographerProvider: ChoreographerProvider) {
public enum class CallbackType(internal val order: Int) {
/** For use by perf markers that need to happen immediately after draw */
PERF_MARKERS(0),
/** For use by [com.facebook.react.uimanager.UIManagerModule] */
DISPATCH_UI(1),
/** For use by [com.facebook.react.animated.NativeAnimatedModule] */
NATIVE_ANIMATED_MODULE(2),
/** Events that make JS do things. */
TIMERS_EVENTS(3),
/**
* Event used to trigger the idle callback. Called after all UI work has been dispatched to JS.
*/
IDLE_EVENT(4)
}

private var choreographer: ChoreographerProvider.Choreographer? = null
private val callbackQueues: Array<ArrayDeque<Choreographer.FrameCallback>> =
Array(CallbackType.entries.size) { ArrayDeque() }
private var totalCallbacks = 0
private var hasPostedCallback = false

private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
synchronized(callbackQueues) {

// Callbacks run once and are then automatically removed, the callback will
// be posted again from postFrameCallback
hasPostedCallback = false
for (i in callbackQueues.indices) {
val callbackQueue = callbackQueues[i]
val initialLength = callbackQueue.size
for (callback in 0 until initialLength) {
val frameCallback = callbackQueue.pollFirst()
if (frameCallback != null) {
frameCallback.doFrame(frameTimeNanos)
totalCallbacks--
} else {
FLog.e(ReactConstants.TAG, "Tried to execute non-existent frame callback")
}
}
}
maybeRemoveFrameCallback()
}
}

init {
UiThreadUtil.runOnUiThread { choreographer = choreographerProvider.getChoreographer() }
}

public fun postFrameCallback(type: CallbackType, callback: Choreographer.FrameCallback) {
synchronized(callbackQueues) {
callbackQueues[type.order].addLast(callback)
totalCallbacks++
Assertions.assertCondition(totalCallbacks > 0)
if (!hasPostedCallback) {
if (choreographer == null) {
// Schedule on the main thread, at which point the constructor's async work will have
// completed
UiThreadUtil.runOnUiThread {
synchronized(callbackQueues) { postFrameCallbackOnChoreographer() }
}
} else {
postFrameCallbackOnChoreographer()
}
}
}
}

public fun removeFrameCallback(type: CallbackType, frameCallback: Choreographer.FrameCallback?) {
synchronized(callbackQueues) {
if (callbackQueues[type.order].removeFirstOccurrence(frameCallback)) {
totalCallbacks--
maybeRemoveFrameCallback()
} else {
FLog.e(ReactConstants.TAG, "Tried to remove non-existent frame callback")
}
}
}

/**
* This method writes on mHasPostedCallback and it should be called from another method that has
* the lock on [callbackQueues].
*/
private fun postFrameCallbackOnChoreographer() {
choreographer?.postFrameCallback(frameCallback)
hasPostedCallback = true
}

/**
* This method reads and writes on mHasPostedCallback and it should be called from another method
* that already has the lock on [callbackQueues].
*/
private fun maybeRemoveFrameCallback() {
Assertions.assertCondition(totalCallbacks >= 0)
if (totalCallbacks == 0 && hasPostedCallback) {
choreographer?.removeFrameCallback(frameCallback)
hasPostedCallback = false
}
}

public companion object {
private var choreographer: ReactChoreographer? = null

@JvmStatic
public fun initialize(choreographerProvider: ChoreographerProvider) {
if (choreographer == null) {
choreographer = ReactChoreographer(choreographerProvider)
}
}

@JvmStatic
public fun getInstance(): ReactChoreographer =
checkNotNull(choreographer) { "ReactChoreographer needs to be initialized." }

@VisibleForTesting
internal fun overrideInstanceForTest(instance: ReactChoreographer?): ReactChoreographer? =
choreographer.also { choreographer = instance }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ class TouchEventDispatchTest {
private lateinit var eventDispatcher: EventDispatcher
private lateinit var uiManager: FabricUIManager
private lateinit var arguments: MockedStatic<Arguments>
private lateinit var reactChoreographer: MockedStatic<ReactChoreographer>
private var reactChoreographerOriginal: ReactChoreographer? = null

@Before
fun setUp() {
Expand Down Expand Up @@ -492,16 +492,13 @@ class TouchEventDispatchTest {

// Ignore scheduled choreographer work
val reactChoreographerMock = mock(ReactChoreographer::class.java)
reactChoreographer = mockStatic(ReactChoreographer::class.java)
reactChoreographer
.`when`<ReactChoreographer> { ReactChoreographer.getInstance() }
.thenReturn(reactChoreographerMock)
reactChoreographerOriginal = ReactChoreographer.overrideInstanceForTest(reactChoreographerMock)
}

@After
fun tearDown() {
arguments.close()
reactChoreographer.close()
ReactChoreographer.overrideInstanceForTest(reactChoreographerOriginal)
}

@Test
Expand Down
Loading

0 comments on commit 9d383dd

Please sign in to comment.