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 @@ -27,6 +27,7 @@
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat.FocusDirection;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
Expand Down Expand Up @@ -65,6 +66,7 @@
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 @@ -934,7 +936,8 @@ private void scheduleMountItem(
long layoutEndTime,
long finishTransactionStartTime,
long finishTransactionEndTime,
int affectedLayoutNodesCount) {
int affectedLayoutNodesCount,
boolean synchronous) {
// When Binding.cpp calls scheduleMountItems during a commit phase, it always calls with
// a BatchMountItem. No other sites call into this with a BatchMountItem, and Binding.cpp only
// calls scheduleMountItems with a BatchMountItem.
Expand All @@ -948,8 +951,9 @@ private void scheduleMountItem(
} else {
shouldSchedule = mountItem != null;
}
// In case of sync rendering, this could be called on the UI thread. Otherwise,
// it should ~always be called on the JS thread.
// In the push model, this is ~always called on the JS thread, or on the UI
// thread in case of sync rendering.
// In the pull model, this is always called on the UI thread, at pull time.
for (UIManagerListener listener : mListeners) {
listener.didScheduleMountItems(this);
}
Expand All @@ -964,16 +968,22 @@ private void scheduleMountItem(

if (shouldSchedule) {
Assertions.assertNotNull(mountItem, "MountItem is null");
mMountItemDispatcher.addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
Runnable runnable =
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
mMountItemDispatcher.tryDispatchMountItems();
}
};
runnable.run();
if (synchronous) {
// 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);
} else {
mMountItemDispatcher.addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
Runnable runnable =
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
mMountItemDispatcher.tryDispatchMountItems();
}
};
runnable.run();
}
}
}

Expand Down Expand Up @@ -1009,6 +1019,26 @@ 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.
*/
@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
@VisibleForTesting
void onTransactionAvailable(int surfaceId) {
FabricUIManagerBinding binding = mBinding;
if (binding == null) {
return;
}
mMountItemDispatcher.addMountItem(new PullTransactionMountItem(surfaceId, binding));
if (UiThreadUtil.isOnUiThread()) {
mMountItemDispatcher.tryDispatchMountItems();
}
}

@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ internal class FabricUIManagerBinding : HybridClassBase() {

external fun reportMount(surfaceId: Int)

external fun pullAndExecuteTransaction(surfaceId: Int)

external fun mergeReactRevision(surfaceId: Int)

fun register(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.mountitems

import com.facebook.proguard.annotations.DoNotStripAny
import com.facebook.react.fabric.FabricUIManagerBinding
import com.facebook.react.fabric.mounting.MountingManager

/**
* Pull model mount item. Enqueued (on the commit thread) when C++ notifies that a transaction is
* available for a surface. When it executes on the UI thread it asks C++ to pull the surface's
* pending transaction and apply it synchronously, so the diff + batch construction happens on the
* UI thread instead of the commit thread (matching iOS).
*/
@DoNotStripAny
internal class PullTransactionMountItem(
private val surfaceId: Int,
private val binding: FabricUIManagerBinding,
) : MountItem {

override fun execute(mountingManager: MountingManager) {
binding.pullAndExecuteTransaction(surfaceId)
}

override fun getSurfaceId(): Int = surfaceId

override fun toString(): String = "PullTransactionMountItem [surfaceId: $surfaceId]"
}
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<<df3f81d0083a8ebe9d0fbcb86e652405>>
* @generated SignedSource<<8508c768e49199737ce5fd1071ca1d68>>
*/

/**
Expand Down Expand Up @@ -252,6 +252,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
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. Implies rawProps accumulation (enableAccumulatedUpdatesInRawPropsAndroid behavior).
*/
@JvmStatic
public fun enableMountingCoordinatorPullModelAndroid(): Boolean = accessor.enableMountingCoordinatorPullModelAndroid()

/**
* Enables the MutationObserver Web API in React Native.
*/
Expand Down
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<<c31110405f1c9cd5ae0627fb114585a6>>
* @generated SignedSource<<eeb2c4625ae5039dc087f364713050fe>>
*/

/**
Expand Down Expand Up @@ -57,6 +57,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
private var enableMountingCoordinatorPullModelAndroidCache: Boolean? = null
private var enableMutationObserverByDefaultCache: Boolean? = null
private var enableNativeCSSParsingCache: Boolean? = null
private var enableNetworkEventReportingCache: Boolean? = null
Expand Down Expand Up @@ -441,6 +442,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun enableMountingCoordinatorPullModelAndroid(): Boolean {
var cached = enableMountingCoordinatorPullModelAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableMountingCoordinatorPullModelAndroid()
enableMountingCoordinatorPullModelAndroidCache = cached
}
return cached
}

override fun enableMutationObserverByDefault(): Boolean {
var cached = enableMutationObserverByDefaultCache
if (cached == null) {
Expand Down
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<<87b76f5c2af82462dc66c7721cd66ac4>>
* @generated SignedSource<<9a171a03d15885e64b84f03442b304b4>>
*/

/**
Expand Down Expand Up @@ -102,6 +102,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun enableModuleArgumentNSNullConversionIOS(): Boolean

@DoNotStrip @JvmStatic public external fun enableMountingCoordinatorPullModelAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun enableMutationObserverByDefault(): Boolean

@DoNotStrip @JvmStatic public external fun enableNativeCSSParsing(): Boolean
Expand Down
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<<8a117541421f413e517e1be1acf8be7d>>
* @generated SignedSource<<4fd762a8e3ab6de1276a15368075f40c>>
*/

/**
Expand Down Expand Up @@ -97,6 +97,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun enableModuleArgumentNSNullConversionIOS(): Boolean = false

override fun enableMountingCoordinatorPullModelAndroid(): Boolean = false

override fun enableMutationObserverByDefault(): Boolean = false

override fun enableNativeCSSParsing(): Boolean = false
Expand Down
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<<9a2806b3934d130010954a92a7b48e0f>>
* @generated SignedSource<<4a4a234004196595b5fb2949bd91f9c4>>
*/

/**
Expand Down Expand Up @@ -61,6 +61,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
private var enableMountingCoordinatorPullModelAndroidCache: Boolean? = null
private var enableMutationObserverByDefaultCache: Boolean? = null
private var enableNativeCSSParsingCache: Boolean? = null
private var enableNetworkEventReportingCache: Boolean? = null
Expand Down Expand Up @@ -482,6 +483,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}

override fun enableMountingCoordinatorPullModelAndroid(): Boolean {
var cached = enableMountingCoordinatorPullModelAndroidCache
if (cached == null) {
cached = currentProvider.enableMountingCoordinatorPullModelAndroid()
accessedFeatureFlags.add("enableMountingCoordinatorPullModelAndroid")
enableMountingCoordinatorPullModelAndroidCache = cached
}
return cached
}

override fun enableMutationObserverByDefault(): Boolean {
var cached = enableMutationObserverByDefaultCache
if (cached == null) {
Expand Down
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<<49c76b8072c1bd29135086c9e0a5da2f>>
* @generated SignedSource<<0b4bf7de06ad677b0868640c98ff0aa9>>
*/

/**
Expand Down Expand Up @@ -97,6 +97,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun enableModuleArgumentNSNullConversionIOS(): Boolean

@DoNotStrip public fun enableMountingCoordinatorPullModelAndroid(): Boolean

@DoNotStrip public fun enableMutationObserverByDefault(): Boolean

@DoNotStrip public fun enableNativeCSSParsing(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ void FabricMountingManager::onSurfaceStop(SurfaceId surfaceId) {
allocatedViewRegistry_.erase(surfaceId);
}

void FabricMountingManager::onTransactionAvailable(SurfaceId surfaceId) {
static auto onTransactionAvailable =
JFabricUIManager::javaClassStatic()->getMethod<void(jint)>(
"onTransactionAvailable");
onTransactionAvailable(javaUIManager_, surfaceId);
}

bool FabricMountingManager::isViewAllocated(SurfaceId surfaceId, Tag tag) {
std::lock_guard lock(allocatedViewsMutex_);
auto it = allocatedViewRegistry_.find(surfaceId);
Expand Down Expand Up @@ -339,7 +346,10 @@ jni::local_ref<jobject> getProps(

return ReadableNativeMap::newObjectCxxArgs(std::move(diff));
}
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
// With the pull model we need to have the accumulated props, as we might skip
// intermediate commits
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() ||
ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
if (oldProps == nullptr) {
return ReadableNativeMap::newObjectCxxArgs(newProps->rawProps);
} else {
Expand Down Expand Up @@ -568,7 +578,8 @@ inline void writeUpdateOverflowInsetMountItem(
} // namespace

void FabricMountingManager::executeMount(
const MountingTransaction& transaction) {
const MountingTransaction& transaction,
bool synchronous) {
TraceSection section("FabricMountingManager::executeMount");

std::scoped_lock lock(commitMutex_);
Expand Down Expand Up @@ -830,7 +841,8 @@ void FabricMountingManager::executeMount(
jlong,
jlong,
jlong,
jint)>("scheduleMountItem");
jint,
jboolean)>("scheduleMountItem");

if (batchMountItemIntsSize == 0) {
auto finishTransactionEndTime = telemetryTimePointNow();
Expand All @@ -845,7 +857,8 @@ void FabricMountingManager::executeMount(
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
telemetryTimePointToMilliseconds(finishTransactionStartTime),
telemetryTimePointToMilliseconds(finishTransactionEndTime),
telemetry.getAffectedLayoutNodesCount());
telemetry.getAffectedLayoutNodesCount(),
static_cast<jboolean>(synchronous));
return;
}

Expand Down Expand Up @@ -1012,7 +1025,8 @@ void FabricMountingManager::executeMount(
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
telemetryTimePointToMilliseconds(finishTransactionStartTime),
telemetryTimePointToMilliseconds(finishTransactionEndTime),
telemetry.getAffectedLayoutNodesCount());
telemetry.getAffectedLayoutNodesCount(),
static_cast<jboolean>(synchronous));

env->DeleteLocalRef(buffer.ints);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,23 @@ class FabricMountingManager final {
*/
bool isViewAllocated(SurfaceId surfaceId, Tag tag);

void executeMount(const MountingTransaction &transaction);
/*
* Converts the transaction's mutations into an IntBufferBatchMountItem and
* hands it to Java.
*
* In the push model (`synchronous` = false), the batch is
* scheduled onto the UI thread asynchronously.
*
* In the pull model (`synchronous` = true) the batch is
* applied immediately on the calling (UI) thread.
*/
void executeMount(const MountingTransaction &transaction, bool synchronous = false);

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

void dispatchCommand(const ShadowView &shadowView, const std::string &commandName, const folly::dynamic &args);

Expand Down
Loading
Loading