Skip to content
Closed
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 @@ -299,6 +299,11 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :

preOperations.executeBatch(batchNumber, nodesManager)
operations.executeBatch(batchNumber, nodesManager)

// Operations executed above may have started animations (e.g. startAnimatingNode); the
// frame callback disarms itself when no animations are active, so re-arm it here.
// didDispatchMountItems is UI-confined, like enqueueFrameCallback.
enqueueFrameCallback()
}

// For non-FabricUIManager only (no-op since Fabric is the only supported UIManager)
Expand Down Expand Up @@ -348,9 +353,11 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
val nodesManager = nodesManager ?: return
if (nodesManager.hasActiveAnimations()) {
nodesManager.runUpdates(frameTimeNanos)
// Only keep the Choreographer armed while animations are actually running.
// didDispatchMountItems re-arms this callback when new animation operations
// (e.g. startAnimatingNode) execute.
enqueueFrameCallback()
}

enqueueFrameCallback()
} catch (ex: Exception) {
throw RuntimeException(ex)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,12 @@ public void doFrameGuarded(long frameTimeNanos) {
mIsMountingEnabled = false;
throw ex;
} finally {
schedule();
// Keep the Choreographer armed only while items remain pending; posting new items
// calls schedule() directly. An unconditional re-schedule here kept the Choreographer
// running at vsync rate while idle.
if (mMountItemDispatcher.hasPendingItems()) {
schedule();
}
}

if (ReactNativeFeatureFlags.useSharedAnimatedBackend() && mBinding != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ internal class MountItemDispatcher(
private val mountItems: Queue<MountItem> = ConcurrentLinkedQueue()
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()

/** @return true if any mount items, pre-mount items or view commands are still pending */
fun hasPendingItems(): Boolean =
!viewCommandMountItems.isEmpty() || !mountItems.isEmpty() || !preMountItems.isEmpty()

private var inDispatch: Boolean = false
var batchedExecutionTime: Long = 0L
private set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ public open class JavaTimerManager(
synchronized(timerGuard) {
timers.add(timer)
timerIdsToTimers.put(timerId, timer)
if (!frameCallbackPosted && !isPaused.get()) {
// Re-arm the timers frame callback lazily: it disarms itself whenever the queue drains.
reactChoreographer.postFrameCallback(
ReactChoreographer.CallbackType.TIMERS_EVENTS,
timerFrameCallback,
)
frameCallbackPosted = true
}
}
}

Expand Down Expand Up @@ -292,6 +300,7 @@ public open class JavaTimerManager(
return
}
val frameTimeMillis = frameTimeNanos / 1000000
var shouldRepost: Boolean
synchronized(timerGuard) {
while (!timers.isEmpty() && timers.peek()!!.targetTime < frameTimeMillis) {
var timer = timers.poll()
Expand All @@ -309,12 +318,20 @@ public open class JavaTimerManager(
timerIdsToTimers.remove(timer.timerId)
}
}
shouldRepost = timers.isNotEmpty()
if (!shouldRepost) {
// The timer queue is empty: disarm instead of re-posting this callback at vsync rate.
// createTimer re-arms the callback when a new timer arrives.
frameCallbackPosted = false
}
}
timersToCall?.let { timers ->
javaScriptTimerExecutor.callTimers(timers)
timersToCall = null
}
reactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this)
if (shouldRepost) {
reactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,13 @@ internal class FabricEventDispatcher(
override fun doFrame(frameTimeNanos: Long) {
UiThreadUtil.assertOnUiThread()

// This callback is one-shot per schedule request (see maybeDispatchBatchedEvents): events
// are dispatched synchronously in dispatchEvent, so there is nothing to re-post here.
// Re-posting unconditionally kept the Choreographer armed at vsync rate while idle.
isFrameCallbackDispatchScheduled = false

if (shouldStop) {
isFrameCallbackDispatchScheduled = false
} else {
dispatchBatchedEvents()
return
}

Systrace.beginSection(Systrace.TRACE_TAG_REACT, "BatchEventDispatchedListeners")
Expand Down
Loading