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 @@ -21,6 +21,7 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Point;
import android.os.Debug;
import android.os.SystemClock;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
Expand Down Expand Up @@ -58,8 +59,10 @@
import com.facebook.react.fabric.internal.interop.InteropUIBlockListener;
import com.facebook.react.fabric.interop.UIBlock;
import com.facebook.react.fabric.interop.UIBlockViewResolver;
import com.facebook.react.fabric.mounting.DisplayFrameInterval;
import com.facebook.react.fabric.mounting.MountItemDispatcher;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.fabric.mounting.PreMountItemsBetweenFrames;
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
import com.facebook.react.fabric.mounting.mountitems.BatchMountItem;
import com.facebook.react.fabric.mounting.mountitems.BatchedAnimatedPropsMountItem;
Expand Down Expand Up @@ -183,6 +186,8 @@ public class FabricUIManager
private final MountingManager mMountingManager;
private final FabricEventDispatcher mEventDispatcher;
private final MountItemDispatcher mMountItemDispatcher;
private final DisplayFrameInterval mDisplayFrameInterval;
private final PreMountItemsBetweenFrames mPreMountItemsBetweenFrames;
private final ViewManagerRegistry mViewManagerRegistry;

private final TextEffectRegistry mTextEffectRegistry = new TextEffectRegistry();
Expand Down Expand Up @@ -254,6 +259,14 @@ public FabricUIManager(
mMountingManager = new MountingManager(viewManagerRegistry, mMountItemExecutor);
mMountItemDispatcher =
new MountItemDispatcher(mMountingManager, new MountItemDispatchListener());
mDisplayFrameInterval = new DisplayFrameInterval(reactContext);
mPreMountItemsBetweenFrames =
new PreMountItemsBetweenFrames(
mMountItemDispatcher,
mDisplayFrameInterval,
Debug::threadCpuTimeNanos,
UiThreadUtil.getUiThreadHandler(),
mDispatchUIFrameCallback::handlePreMountException);
mEventDispatcher = new FabricEventDispatcher(reactContext, new FabricEventEmitter(this));
mBatchEventDispatchedListener = batchEventDispatchedListener;
mReactApplicationContext.addLifecycleEventListener(this);
Expand Down Expand Up @@ -459,6 +472,9 @@ public void stopSurface(final int surfaceID) {
@Override
public void initialize() {
mEventDispatcher.addBatchEventDispatchedListener(mBatchEventDispatchedListener);
if (ReactNativeFeatureFlags.enablePremountItemsBetweenFramesAndroid()) {
mDisplayFrameInterval.start();
}
if (ReactNativeFeatureFlags.enableFabricLogs()) {
mDevToolsReactPerfLogger = new DevToolsReactPerfLogger();
mDevToolsReactPerfLogger.addDevToolsReactPerfLoggerListener(FABRIC_PERF_LOGGER);
Expand Down Expand Up @@ -494,6 +510,7 @@ public void invalidate() {

mEventDispatcher.removeBatchEventDispatchedListener(mBatchEventDispatchedListener);
mEventDispatcher.invalidate();
mDisplayFrameInterval.stop();

mReactApplicationContext.unregisterComponentCallbacks(mViewManagerRegistry);
mViewManagerRegistry.invalidate();
Expand Down Expand Up @@ -1676,10 +1693,19 @@ void resume() {
void pause() {
ReactChoreographer.getInstance()
.removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this);
mPreMountItemsBetweenFrames.cancel();
mShouldSchedule = false;
mIsScheduled = false;
}

@UiThread
@ThreadConfined(UI)
void handlePreMountException(Exception exception) {
FLog.e(TAG, "Exception thrown when pre-mounting between frames", exception);
mIsMountingEnabled = false;
mReactApplicationContext.getExceptionHandler().handleException(exception);
}

@Override
@UiThread
@ThreadConfined(UI)
Expand Down Expand Up @@ -1719,13 +1745,19 @@ public void doFrameGuarded(long frameTimeNanos) {
}

try {
// First, execute as many pre mount items as we can within frameTimeNanos time.
// If not all pre mount items were executed, following may happen:
// 1. In case there are view commands or mount items in MountItemDispatcher: execute
// remaining pre mount items.
// 2. In case there are no view commands or mount items, wait until next frame.
mMountItemDispatcher.dispatchPreMountItems(frameTimeNanos);
mMountItemDispatcher.tryDispatchMountItems();
// View commands and mount items run every pending pre-mount item before themselves. What
// is left runs either here, for half of a 60 Hz frame, or here for as long as the last
// frame's draw slept and after this frame's traversal until the next vsync, queued only
// once this frame's mount items have run, so a frame that fails to mount leaves nothing to
// pre-mount after it.
if (ReactNativeFeatureFlags.enablePremountItemsBetweenFramesAndroid()) {
mPreMountItemsBetweenFrames.preMountBeforeTraversal();
mMountItemDispatcher.tryDispatchMountItems();
mPreMountItemsBetweenFrames.scheduleAfterFrame(frameTimeNanos);
} else {
mMountItemDispatcher.dispatchPreMountItems(frameTimeNanos);
mMountItemDispatcher.tryDispatchMountItems();
}
} catch (Exception ex) {
FLog.e(TAG, "Exception thrown when executing UIFrameGuarded", ex);
mIsMountingEnabled = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.fabric.mounting

import android.content.Context
import android.hardware.display.DisplayManager
import android.view.Display
import com.facebook.react.bridge.UiThreadUtil

/**
* The default display's frame interval at its current refresh rate. It is read again when the
* display reports a change, so a frame never reads the display itself.
*/
internal class DisplayFrameInterval(context: Context) :
PreMountItemsBetweenFrames.FrameIntervalSource, DisplayManager.DisplayListener {
private val displayManager: DisplayManager? = context.getSystemService(DisplayManager::class.java)

@Volatile private var frameIntervalNanos: Long? = null

fun start() {
// From Android 16 a listener gets refresh-rate changes only once the process has read the rate.
frameIntervalNanos = readFrameIntervalNanos()
displayManager?.registerDisplayListener(this, UiThreadUtil.getUiThreadHandler())
}

fun stop() {
displayManager?.unregisterDisplayListener(this)
}

override fun frameIntervalNanos(): Long? = frameIntervalNanos

override fun onDisplayChanged(displayId: Int) {
if (displayId == Display.DEFAULT_DISPLAY) {
frameIntervalNanos = readFrameIntervalNanos()
}
}

override fun onDisplayAdded(displayId: Int): Unit = Unit

override fun onDisplayRemoved(displayId: Int): Unit = Unit

private fun readFrameIntervalNanos(): Long? {
val refreshRate = displayManager?.getDisplay(Display.DEFAULT_DISPLAY)?.refreshRate ?: 0f
return if (refreshRate > 0f) (NANOSECONDS_PER_SECOND / refreshRate).toLong() else null
}

private companion object {
private const val NANOSECONDS_PER_SECOND: Double = 1_000_000_000.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

package com.facebook.react.fabric.mounting

import android.os.SystemClock
import android.view.View
import androidx.annotation.UiThread
import com.facebook.common.logging.FLog
Expand All @@ -17,6 +16,7 @@ import com.facebook.react.bridge.ReactIgnorableMountingException
import com.facebook.react.bridge.ReactNoCrashSoftException
import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.bridge.RetryableMountingLayerException
import com.facebook.react.common.SystemClock
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem
import com.facebook.react.fabric.mounting.mountitems.MountItem
Expand All @@ -35,8 +35,10 @@ internal class MountItemDispatcher(
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()
fun hasPendingItems(): Boolean = hasMountItems() || hasPreMountItems()

/** @return true if any mount items or view commands are waiting to be dispatched */
fun hasMountItems(): Boolean = !viewCommandMountItems.isEmpty() || !mountItems.isEmpty()

// Items can be queued from any thread while the DISPATCH_UI frame callback is disarmed at
// idle (see FabricUIManager's doFrameGuarded); the listener re-arms it.
Expand Down Expand Up @@ -340,6 +342,23 @@ internal class MountItemDispatcher(
dispatchPreMountItemsImpl(deadline)
}

fun hasPreMountItems(): Boolean = !preMountItems.isEmpty()

/**
* Executes pre-mount items until [deadlineNanos], and returns whether any are left. An item
* starts only while the deadline has not passed, so the last one can run past it by its own
* duration.
*/
@UiThread
@ThreadConfined(UI)
fun dispatchPreMountItemsUntil(deadlineNanos: Long): Boolean {
if (preMountItems.isEmpty()) {
return false
}
dispatchPreMountItemsImpl(deadlineNanos)
return !preMountItems.isEmpty()
}

private fun dispatchPreMountItemsImpl(deadline: Long) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")

Expand All @@ -355,7 +374,7 @@ internal class MountItemDispatcher(

try {
while (true) {
if (System.nanoTime() > deadline) {
if (SystemClock.nanoTime() > deadline) {
break
}

Expand Down
Loading
Loading