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 @@ -67,7 +67,6 @@
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItemFactory;
import com.facebook.react.fabric.mounting.mountitems.PrefetchResourcesMountItem;
import com.facebook.react.fabric.mounting.mountitems.PullTransactionMountItem;
import com.facebook.react.fabric.mounting.mountitems.SynchronousMountItem;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
Expand Down Expand Up @@ -237,6 +236,11 @@ public void executeItems(Queue<MountItem> items) {
// This executor can be technically accessed before the dispatcher is created,
// but if that happens, something is terribly wrong
mMountItemDispatcher.dispatchMountItems(items);
// Only called when a surface's root view attaches, before the surface counts as attached,
// so the dispatch above skips its pull. Pull model: the next frame pulls it.
if (mMountItemDispatcher.hasSurfaceIdsToPull()) {
mDispatchUIFrameCallback.schedule();
}
}
};

Expand Down Expand Up @@ -1015,15 +1019,9 @@ private void scheduleMountItem(
if (shouldSchedule) {
Assertions.assertNotNull(mountItem, "MountItem is null");
if (synchronous) {
if (mMountingManager.isWaitingForViewAttach(mountItem.getSurfaceId())) {
// Regular mount items are still being deferred into the surface's attach queue.
// Executing this batch inline would run it ahead of the preallocations queued there.
mMountItemDispatcher.addMountItem(mountItem);
} else {
// Pull model: we are already on the UI thread, inside the dispatcher's loop executing
// a PullTransactionMountItem. We don't schedule the item, we execute it directly.
mountItem.execute(mMountingManager);
}
// Pull model: we are already on the UI thread, inside the dispatcher's pull of a surface
// whose root view is attached. We don't schedule the item, we execute it directly.
mountItem.execute(mMountingManager);
} else {
mMountItemDispatcher.addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
Expand Down Expand Up @@ -1073,19 +1071,18 @@ public void runGuarded() {

/**
* Pull model: called from C++ via JNI (usually on the commit thread) to signal that a transaction
* is available for {@code surfaceId}. Enqueues a PullTransactionMountItem so the UI thread pulls
* and applies the transaction itself, preserving mount-item ordering.
* is available for {@code surfaceId}. The next dispatch on the UI thread pulls and applies it
* before the queued mount items.
*/
@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
@VisibleForTesting
void onTransactionAvailable(int surfaceId) {
FabricUIManagerBinding binding = mBinding;
if (binding == null) {
if (mBinding == null) {
return;
}
mMountItemDispatcher.addMountItem(new PullTransactionMountItem(surfaceId, binding));
mMountItemDispatcher.addSurfaceIdToPull(surfaceId);
if (UiThreadUtil.isOnUiThread()) {
mMountItemDispatcher.tryDispatchMountItems();
}
Expand Down Expand Up @@ -1550,16 +1547,14 @@ public void didMountItems(@Nullable List<? extends MountItem> mountItems) {
listener.didMountItems(FabricUIManager.this);
}

if (mountItems == null || mountItems.isEmpty()) {
return;
}

// Collect surface IDs for all the mount items
for (MountItem mountItem : mountItems) {
if (mountItem != null
&& mountItem.getSurfaceId() != View.NO_ID
&& !mSurfaceIdsWithPendingMountNotification.contains(mountItem.getSurfaceId())) {
mSurfaceIdsWithPendingMountNotification.add(mountItem.getSurfaceId());
if (mountItems != null) {
// Collect surface IDs for all the mount items
for (MountItem mountItem : mountItems) {
if (mountItem != null
&& mountItem.getSurfaceId() != View.NO_ID
&& !mSurfaceIdsWithPendingMountNotification.contains(mountItem.getSurfaceId())) {
mSurfaceIdsWithPendingMountNotification.add(mountItem.getSurfaceId());
}
}
}

Expand Down Expand Up @@ -1590,6 +1585,20 @@ public void didMountItems(@Nullable List<? extends MountItem> mountItems) {
}
}

@UiThread
@ThreadConfined(UI)
@Override
public void pullTransaction(int surfaceId) {
FabricUIManagerBinding binding = mBinding;
if (binding == null) {
return;
}
binding.pullAndExecuteTransaction(surfaceId);
if (!mSurfaceIdsWithPendingMountNotification.contains(surfaceId)) {
mSurfaceIdsWithPendingMountNotification.add(surfaceId);
}
}

@Override
public void didDispatchMountItems() {
for (UIManagerListener listener : mListeners) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.tracing.PerformanceTracer
import com.facebook.systrace.Systrace
import java.util.Queue
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue

internal class MountItemDispatcher(
Expand All @@ -33,10 +34,20 @@ internal class MountItemDispatcher(
private val viewCommandMountItems: Queue<DispatchCommandMountItem> = ConcurrentLinkedQueue()
private val mountItems: Queue<MountItem> = ConcurrentLinkedQueue()
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()
private val surfaceIdsToPull: MutableSet<Int> = ConcurrentHashMap.newKeySet()

/** @return true if any mount items, pre-mount items or view commands are still pending */
/**
* @return true if any mount items, pre-mount items, view commands or pulls of surfaces with an
* attached root view are still pending. The attach schedules the frame that pulls its surface.
*/
fun hasPendingItems(): Boolean =
!viewCommandMountItems.isEmpty() || !mountItems.isEmpty() || !preMountItems.isEmpty()
!viewCommandMountItems.isEmpty() ||
!mountItems.isEmpty() ||
!preMountItems.isEmpty() ||
surfaceIdsToPull.any { !mountingManager.isWaitingForViewAttach(it) }

/** Pull model: @return true if any surface has a transaction waiting to be pulled. */
fun hasSurfaceIdsToPull(): Boolean = surfaceIdsToPull.isNotEmpty()

// 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 @@ -69,6 +80,12 @@ internal class MountItemDispatcher(
notifyItemsQueued()
}

/** Pull model: a pull mounts the surface's latest revision, so it is pending at most once. */
fun addSurfaceIdToPull(surfaceId: Int) {
surfaceIdsToPull.add(surfaceId)
notifyItemsQueued()
}

fun addPreAllocateMountItem(mountItem: MountItem) {
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
// MountItem - because PreAllocateViewMountItem are not batched, and is relatively more
Expand Down Expand Up @@ -147,9 +164,10 @@ internal class MountItemDispatcher(
* Executes view commands, pre mount items and mount items in the respective order:
* 1. View commands.
* 2. Pre mount items.
* 3. Regular mount items.
* 3. Pulls of surfaces with an available transaction and an attached root view (pull model).
* 4. Regular mount items.
*
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
* Does nothing if there are no view commands, mount items or surfaces to pull.
* Nothing should call this directly except for `tryDispatchMountItems`.
*/
@UiThread
Expand All @@ -160,8 +178,13 @@ internal class MountItemDispatcher(

val viewCommandMountItemsToDispatch = getAndResetViewCommandMountItems()
val mountItemsToDispatch = getAndResetMountItems()
val attachedSurfaceIdsToPull = getAttachedSurfaceIdsToPull()

if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
if (
mountItemsToDispatch == null &&
viewCommandMountItemsToDispatch == null &&
attachedSurfaceIdsToPull == null
) {
return
}

Expand Down Expand Up @@ -256,7 +279,8 @@ internal class MountItemDispatcher(
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}

mountItemsToDispatch?.let { items ->
if (mountItemsToDispatch != null || attachedSurfaceIdsToPull != null) {
val items = mountItemsToDispatch.orEmpty()
Systrace.beginSection(
Systrace.TRACE_TAG_REACT,
"MountItemDispatcher::mountViews mountItems to execute",
Expand All @@ -269,6 +293,16 @@ internal class MountItemDispatcher(
{ ->
val batchedExecutionStartTime = SystemClock.uptimeMillis()

for (surfaceId in attachedSurfaceIdsToPull.orEmpty()) {
surfaceIdsToPull.remove(surfaceId)
try {
itemDispatchListener.pullTransaction(surfaceId)
} catch (e: Throwable) {
FLog.e(TAG, "dispatchMountItems: caught exception pulling surface $surfaceId", e)
logOrRethrowMountingException(e, surfaceId)
}
}

for (mountItem in items) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
Expand Down Expand Up @@ -298,15 +332,7 @@ internal class MountItemDispatcher(
}
}

if (mountItem.getSurfaceId() != View.NO_ID) {
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
}

if (ReactIgnorableMountingException.isIgnorable(e)) {
ReactSoftExceptionLogger.logSoftException(TAG, e)
} else {
throw e
}
logOrRethrowMountingException(e, mountItem.getSurfaceId())
}
}
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
Expand Down Expand Up @@ -375,6 +401,18 @@ internal class MountItemDispatcher(
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}

private fun logOrRethrowMountingException(e: Throwable, surfaceId: Int) {
if (surfaceId != View.NO_ID) {
mountingManager.getSurfaceManager(surfaceId)?.printSurfaceState()
}

if (ReactIgnorableMountingException.isIgnorable(e)) {
ReactSoftExceptionLogger.logSoftException(TAG, e)
} else {
throw e
}
}

private fun executeOrEnqueue(item: MountItem) {
if (mountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
Expand Down Expand Up @@ -408,13 +446,23 @@ internal class MountItemDispatcher(
@UiThread
private fun getAndResetPreMountItems(): List<MountItem>? = drainConcurrentItemQueue(preMountItems)

/** A surface waiting for its root view keeps its pull until the frame after the attach. */
@ThreadConfined(UI)
@UiThread
private fun getAttachedSurfaceIdsToPull(): List<Int>? =
if (surfaceIdsToPull.isEmpty()) null
else surfaceIdsToPull.filterNot(mountingManager::isWaitingForViewAttach).ifEmpty { null }

interface ItemDispatchListener {
fun willMountItems(mountItems: List<MountItem>?)

fun didMountItems(mountItems: List<MountItem>?)

fun didDispatchMountItems()

/** Pull model: pulls the surface's latest transaction and mounts it synchronously. */
fun pullTransaction(surfaceId: Int)

/** Called (from any thread) whenever new items are queued into the dispatcher. */
fun onItemsQueued()
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<eb0bafceba60cb439a4be3d68b890725>>
* @generated SignedSource<<7fbc80af8ddd69f5986f2c955272c334>>
*/

/**
Expand Down Expand Up @@ -289,7 +289,7 @@ public object ReactNativeFeatureFlags {
public fun enableModuleArgumentNSNullConversionIOS(): Boolean = accessor.enableModuleArgumentNSNullConversionIOS()

/**
* When enabled, Android mounts transactions with the pull model (like iOS): the commit thread no longer pulls and builds the mount batch in schedulerShouldRenderTransactions. Instead the UI thread pulls the transaction itself via a PullTransactionMountItem enqueued in the MountItemDispatcher, builds the IntBufferBatchMountItem, and applies it synchronously. Requires `enableAccumulatedUpdatesInRawPropsAndroid` to be enabled as well, since a single pull may collapse several commits into one diff and therefore needs complete accumulated rawProps; when used together with Props 2.0, also enable `enableExclusivePropsUpdateAndroid` and `enablePropsUpdateReconciliationAndroid`.
* When enabled, Android mounts transactions with the pull model (like iOS): the commit thread no longer pulls and builds the mount batch in schedulerShouldRenderTransactions. Instead the UI thread pulls the transaction itself at its next mount dispatch, builds the IntBufferBatchMountItem, and applies it synchronously. Requires `enableAccumulatedUpdatesInRawPropsAndroid` to be enabled as well, since a single pull may collapse several commits into one diff and therefore needs complete accumulated rawProps; when used together with Props 2.0, also enable `enableExclusivePropsUpdateAndroid` and `enablePropsUpdateReconciliationAndroid`.
*/
@JvmStatic
public fun enableMountingCoordinatorPullModelAndroid(): Boolean = accessor.enableMountingCoordinatorPullModelAndroid()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FabricMountingManager final {

/*
* Pull model: notify Java that a transaction is available for `surfaceId` so
* the UI thread can pull it via a PullTransactionMountItem.
* the UI thread pulls it at its next mount dispatch.
*/
void onTransactionAvailable(SurfaceId surfaceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,29 +125,12 @@ void FabricUIManagerBinding::pullAndExecuteTransaction(SurfaceId surfaceId) {

std::shared_ptr<const MountingCoordinator> mountingCoordinator;
{
std::shared_lock lock(surfaceHandlerRegistryMutex_);
auto iterator = surfaceHandlerRegistry_.find(surfaceId);
if (iterator == surfaceHandlerRegistry_.end()) {
std::scoped_lock lock(pullableMountingCoordinatorsMutex_);
auto pullable = pullableMountingCoordinators_.extract(surfaceId);
if (pullable.empty()) {
return;
}
const auto* surfaceHandler = std::get_if<SurfaceHandler>(&iterator->second);
jni::local_ref<SurfaceHandlerBinding::jhybridobject> javaSurfaceHandler;
if (surfaceHandler == nullptr) {
javaSurfaceHandler =
std::get<jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>(
iterator->second)
.lockLocal();
if (javaSurfaceHandler) {
surfaceHandler = &javaSurfaceHandler->cthis()->getSurfaceHandler();
}
}
if (surfaceHandler != nullptr) {
mountingCoordinator = surfaceHandler->getMountingCoordinator();
}
}

if (mountingCoordinator == nullptr) {
return;
mountingCoordinator = std::move(pullable.mapped());
}

auto mountingManager = getMountingManager("pullAndExecuteTransaction");
Expand Down Expand Up @@ -730,10 +713,21 @@ void FabricUIManagerBinding::schedulerShouldRenderTransactions(
if (ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
// Pull model: do NOT pull the transaction or build the batch here (on the
// commit thread). Just notify Java that a transaction is available; the UI
// thread will pull it via a PullTransactionMountItem and call back into
// `pullAndExecuteTransaction`.
mountingManager->onTransactionAvailable(
mountingCoordinator->getSurfaceId());
// thread calls back into `pullAndExecuteTransaction` at its next mount
// dispatch.
auto surfaceId = mountingCoordinator->getSurfaceId();
{
std::scoped_lock lock(pullableMountingCoordinatorsMutex_);
auto& pullable = pullableMountingCoordinators_[surfaceId];
if (pullable != nullptr && pullable != mountingCoordinator) {
LOG(WARNING)
<< "FabricUIManagerBinding::schedulerShouldRenderTransactions: "
<< "surface " << surfaceId
<< " was restarted while the previous surface's commit waits to be pulled";
}
pullable = mountingCoordinator;
}
mountingManager->onTransactionAvailable(surfaceId);
} else if (ReactNativeFeatureFlags::
enableAccumulatedUpdatesInRawPropsAndroid()) {
auto mountingTransaction = mountingCoordinator->pullTransaction(
Expand Down
Loading
Loading