Skip to content

Commit e35d94d

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Wire the Android pull model in C++ behind the feature flag
Summary: Makes `enableMountingCoordinatorPullModelAndroid` functional. With the flag on, the commit thread only signals transaction availability and the UI thread pulls and applies at mount time (matching iOS/macOS); with the flag off (default), behavior is byte-for-byte identical. - `schedulerShouldRenderTransactions`: notifies via JNI (`FabricMountingManager::onTransactionAvailable`) instead of pulling and building the batch. - `schedulerDidFinishTransaction`: no-op under the pull model. - `FabricUIManagerBinding::pullAndExecuteTransaction` (new JNI method): pulls the surface's transaction on the UI thread and runs `executeMount`. - `FabricMountingManager::executeMount`: gains a `synchronous` mode that executes the batch directly on the UI thread. - The pull flag implies accumulated rawProps — a correctness requirement so multi-revision diffs stay lossless. ## Changelog: [Android] [Added] - Wire the pull-model mounting path in C++ behind `enableMountingCoordinatorPullModelAndroid` Differential Revision: D112309053
1 parent c4678e8 commit e35d94d

10 files changed

Lines changed: 138 additions & 28 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,8 @@ private void scheduleMountItem(
936936
long layoutEndTime,
937937
long finishTransactionStartTime,
938938
long finishTransactionEndTime,
939-
int affectedLayoutNodesCount) {
939+
int affectedLayoutNodesCount,
940+
boolean synchronous) {
940941
// When Binding.cpp calls scheduleMountItems during a commit phase, it always calls with
941942
// a BatchMountItem. No other sites call into this with a BatchMountItem, and Binding.cpp only
942943
// calls scheduleMountItems with a BatchMountItem.
@@ -950,8 +951,9 @@ private void scheduleMountItem(
950951
} else {
951952
shouldSchedule = mountItem != null;
952953
}
953-
// In case of sync rendering, this could be called on the UI thread. Otherwise,
954-
// it should ~always be called on the JS thread.
954+
// In the push model, this is ~always called on the JS thread, or on the UI
955+
// thread in case of sync rendering.
956+
// In the pull model, this is always called on the UI thread, at pull time.
955957
for (UIManagerListener listener : mListeners) {
956958
listener.didScheduleMountItems(this);
957959
}
@@ -966,16 +968,22 @@ private void scheduleMountItem(
966968

967969
if (shouldSchedule) {
968970
Assertions.assertNotNull(mountItem, "MountItem is null");
969-
mMountItemDispatcher.addMountItem(mountItem);
970-
if (UiThreadUtil.isOnUiThread()) {
971-
Runnable runnable =
972-
new GuardedRunnable(mReactApplicationContext) {
973-
@Override
974-
public void runGuarded() {
975-
mMountItemDispatcher.tryDispatchMountItems();
976-
}
977-
};
978-
runnable.run();
971+
if (synchronous) {
972+
// Pull model: we are already on the UI thread, inside the dispatcher's loop executing
973+
// a PullTransactionMountItem. We don't schedule the item, we execute it directly.
974+
mountItem.execute(mMountingManager);
975+
} else {
976+
mMountItemDispatcher.addMountItem(mountItem);
977+
if (UiThreadUtil.isOnUiThread()) {
978+
Runnable runnable =
979+
new GuardedRunnable(mReactApplicationContext) {
980+
@Override
981+
public void runGuarded() {
982+
mMountItemDispatcher.tryDispatchMountItems();
983+
}
984+
};
985+
runnable.run();
986+
}
979987
}
980988
}
981989

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ void FabricMountingManager::onSurfaceStop(SurfaceId surfaceId) {
5252
allocatedViewRegistry_.erase(surfaceId);
5353
}
5454

55+
void FabricMountingManager::onTransactionAvailable(SurfaceId surfaceId) {
56+
static auto onTransactionAvailable =
57+
JFabricUIManager::javaClassStatic()->getMethod<void(jint)>(
58+
"onTransactionAvailable");
59+
onTransactionAvailable(javaUIManager_, surfaceId);
60+
}
61+
5562
bool FabricMountingManager::isViewAllocated(SurfaceId surfaceId, Tag tag) {
5663
std::lock_guard lock(allocatedViewsMutex_);
5764
auto it = allocatedViewRegistry_.find(surfaceId);
@@ -339,7 +346,10 @@ jni::local_ref<jobject> getProps(
339346

340347
return ReadableNativeMap::newObjectCxxArgs(std::move(diff));
341348
}
342-
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
349+
// With the pull model we need to have the accumulated props, as we might skip
350+
// intermediate commits
351+
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() ||
352+
ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
343353
if (oldProps == nullptr) {
344354
return ReadableNativeMap::newObjectCxxArgs(newProps->rawProps);
345355
} else {
@@ -568,7 +578,8 @@ inline void writeUpdateOverflowInsetMountItem(
568578
} // namespace
569579

570580
void FabricMountingManager::executeMount(
571-
const MountingTransaction& transaction) {
581+
const MountingTransaction& transaction,
582+
bool synchronous) {
572583
TraceSection section("FabricMountingManager::executeMount");
573584

574585
std::scoped_lock lock(commitMutex_);
@@ -830,7 +841,8 @@ void FabricMountingManager::executeMount(
830841
jlong,
831842
jlong,
832843
jlong,
833-
jint)>("scheduleMountItem");
844+
jint,
845+
jboolean)>("scheduleMountItem");
834846

835847
if (batchMountItemIntsSize == 0) {
836848
auto finishTransactionEndTime = telemetryTimePointNow();
@@ -845,7 +857,8 @@ void FabricMountingManager::executeMount(
845857
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
846858
telemetryTimePointToMilliseconds(finishTransactionStartTime),
847859
telemetryTimePointToMilliseconds(finishTransactionEndTime),
848-
telemetry.getAffectedLayoutNodesCount());
860+
telemetry.getAffectedLayoutNodesCount(),
861+
static_cast<jboolean>(synchronous));
849862
return;
850863
}
851864

@@ -1012,7 +1025,8 @@ void FabricMountingManager::executeMount(
10121025
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
10131026
telemetryTimePointToMilliseconds(finishTransactionStartTime),
10141027
telemetryTimePointToMilliseconds(finishTransactionEndTime),
1015-
telemetry.getAffectedLayoutNodesCount());
1028+
telemetry.getAffectedLayoutNodesCount(),
1029+
static_cast<jboolean>(synchronous));
10161030

10171031
env->DeleteLocalRef(buffer.ints);
10181032
}

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,23 @@ class FabricMountingManager final {
5454
*/
5555
bool isViewAllocated(SurfaceId surfaceId, Tag tag);
5656

57-
void executeMount(const MountingTransaction &transaction);
57+
/*
58+
* Converts the transaction's mutations into an IntBufferBatchMountItem and
59+
* hands it to Java.
60+
*
61+
* In the push model (`synchronous` = false), the batch is
62+
* scheduled onto the UI thread asynchronously.
63+
*
64+
* In the pull model (`synchronous` = true) the batch is
65+
* applied immediately on the calling (UI) thread.
66+
*/
67+
void executeMount(const MountingTransaction &transaction, bool synchronous = false);
68+
69+
/*
70+
* Pull model: notify Java that a transaction is available for `surfaceId` so
71+
* the UI thread can pull it via a PullTransactionMountItem.
72+
*/
73+
void onTransactionAvailable(SurfaceId surfaceId);
5874

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

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,55 @@ void FabricUIManagerBinding::reportMount(SurfaceId surfaceId) {
120120
scheduler->reportMount(surfaceId);
121121
}
122122

123+
void FabricUIManagerBinding::pullAndExecuteTransaction(SurfaceId surfaceId) {
124+
TraceSection section("FabricUIManagerBinding::pullAndExecuteTransaction");
125+
126+
std::shared_ptr<const MountingCoordinator> mountingCoordinator;
127+
{
128+
std::shared_lock lock(surfaceHandlerRegistryMutex_);
129+
auto iterator = surfaceHandlerRegistry_.find(surfaceId);
130+
if (iterator == surfaceHandlerRegistry_.end()) {
131+
return;
132+
}
133+
const auto* surfaceHandler = std::get_if<SurfaceHandler>(&iterator->second);
134+
jni::local_ref<SurfaceHandlerBinding::jhybridobject> javaSurfaceHandler;
135+
if (surfaceHandler == nullptr) {
136+
javaSurfaceHandler =
137+
std::get<jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>(
138+
iterator->second)
139+
.lockLocal();
140+
if (javaSurfaceHandler) {
141+
surfaceHandler = &javaSurfaceHandler->cthis()->getSurfaceHandler();
142+
}
143+
}
144+
if (surfaceHandler != nullptr) {
145+
mountingCoordinator = surfaceHandler->getMountingCoordinator();
146+
}
147+
}
148+
149+
if (mountingCoordinator == nullptr) {
150+
return;
151+
}
152+
153+
auto mountingManager = getMountingManager("pullAndExecuteTransaction");
154+
if (!mountingManager) {
155+
return;
156+
}
157+
158+
// The UI thread pulls the transaction itself (it may accumulate several
159+
// revisions committed since the notification was enqueued, and may be empty
160+
// if a previous pull already consumed them). willPerformAsynchronously =
161+
// false: the transaction is applied synchronously right here, so no
162+
// `didPerformAsyncTransactions` bookkeeping is needed.
163+
auto mountingTransaction =
164+
mountingCoordinator->pullTransaction(/* willPerformAsynchronously = */
165+
false);
166+
if (mountingTransaction.has_value()) {
167+
mountingManager->executeMount(
168+
*mountingTransaction, /* synchronous = */ true);
169+
}
170+
}
171+
123172
#pragma mark - Surface management
124173

125174
// Used by bridgeless
@@ -628,9 +677,11 @@ FabricUIManagerBinding::getMountingManager(const char* locationHint) {
628677

629678
void FabricUIManagerBinding::schedulerDidFinishTransaction(
630679
const std::shared_ptr<const MountingCoordinator>& mountingCoordinator) {
631-
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
632-
// We don't do anything here. We will pull the transaction in
633-
// `schedulerShouldRenderTransactions`.
680+
if (ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid() ||
681+
ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
682+
// We don't do anything here. With the pull model, the UI thread pulls the
683+
// transaction itself (see `pullAndExecuteTransaction`). Otherwise we will
684+
// pull the transaction in `schedulerShouldRenderTransactions`.
634685
} else {
635686
// We shouldn't be pulling the transaction here (which triggers diffing of
636687
// the trees to determine the mutations to run on the host platform),
@@ -678,7 +729,15 @@ void FabricUIManagerBinding::schedulerShouldRenderTransactions(
678729
}
679730
}
680731

681-
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
732+
if (ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
733+
// Pull model: do NOT pull the transaction or build the batch here (on the
734+
// commit thread). Just notify Java that a transaction is available; the UI
735+
// thread will pull it via a PullTransactionMountItem and call back into
736+
// `pullAndExecuteTransaction`.
737+
mountingManager->onTransactionAvailable(
738+
mountingCoordinator->getSurfaceId());
739+
} else if (ReactNativeFeatureFlags::
740+
enableAccumulatedUpdatesInRawPropsAndroid()) {
682741
auto mountingTransaction = mountingCoordinator->pullTransaction(
683742
/* willPerformAsynchronously = */ true);
684743
if (mountingTransaction.has_value()) {
@@ -867,6 +926,9 @@ void FabricUIManagerBinding::registerNatives() {
867926
"drainPreallocateViewsQueue",
868927
FabricUIManagerBinding::drainPreallocateViewsQueue),
869928
makeNativeMethod("reportMount", FabricUIManagerBinding::reportMount),
929+
makeNativeMethod(
930+
"pullAndExecuteTransaction",
931+
FabricUIManagerBinding::pullAndExecuteTransaction),
870932
makeNativeMethod(
871933
"uninstallFabricUIManager",
872934
FabricUIManagerBinding::uninstallFabricUIManager),

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
133133

134134
void reportMount(SurfaceId surfaceId);
135135

136+
void pullAndExecuteTransaction(SurfaceId surfaceId);
137+
136138
jint findNextFocusableElement(jint parentTag, jint focusedTag, jint direction);
137139

138140
jintArray getRelativeAncestorList(jint rootTag, jint childTag);

packages/react-native/ReactCommon/react/renderer/core/Props.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ void Props::initializeDynamicProps(
4545
const Props& sourceProps,
4646
const RawProps& rawProps,
4747
const std::function<bool(const std::string&)>& filterObjectKeys) {
48-
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
48+
// The pull model also requires complete rawProps. A single pull may collapse
49+
// several commits into one diff.
50+
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() ||
51+
ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
4952
auto& oldRawProps = sourceProps.rawProps;
5053
auto newRawProps = rawProps.toDynamic(filterObjectKeys);
5154
auto mergedRawProps = mergeDynamicProps(

packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ std::string getShadowTreeCommitSourceName(ShadowTreeCommitSource source) {
4141

4242
inline bool isPropsUpdatesAccumulationGuaranteed() {
4343
#ifdef __ANDROID__
44-
return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid();
44+
// The pull model implies rawProps accumulation (see Props.cpp).
45+
return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() ||
46+
ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid();
4547
#else
4648
return true;
4749
#endif

scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2370,12 +2370,13 @@ class facebook::react::FabricMountingManager {
23702370
public void destroyUnmountedShadowNode(const facebook::react::ShadowNodeFamily& family);
23712371
public void dispatchCommand(const facebook::react::ShadowView& shadowView, const std::string& commandName, const folly::dynamic& args);
23722372
public void drainPreallocateViewsQueue();
2373-
public void executeMount(const facebook::react::MountingTransaction& transaction);
2373+
public void executeMount(const facebook::react::MountingTransaction& transaction, bool synchronous = false);
23742374
public void maybePreallocateShadowNode(const facebook::react::ShadowNode& shadowNode);
23752375
public void onAllAnimationsComplete();
23762376
public void onAnimationStarted();
23772377
public void onSurfaceStart(facebook::react::SurfaceId surfaceId);
23782378
public void onSurfaceStop(facebook::react::SurfaceId surfaceId);
2379+
public void onTransactionAvailable(facebook::react::SurfaceId surfaceId);
23792380
public void preallocateShadowView(const facebook::react::ShadowView& shadowView);
23802381
public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId);
23812382
public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType);

scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2353,12 +2353,13 @@ class facebook::react::FabricMountingManager {
23532353
public void destroyUnmountedShadowNode(const facebook::react::ShadowNodeFamily& family);
23542354
public void dispatchCommand(const facebook::react::ShadowView& shadowView, const std::string& commandName, const folly::dynamic& args);
23552355
public void drainPreallocateViewsQueue();
2356-
public void executeMount(const facebook::react::MountingTransaction& transaction);
2356+
public void executeMount(const facebook::react::MountingTransaction& transaction, bool synchronous = false);
23572357
public void maybePreallocateShadowNode(const facebook::react::ShadowNode& shadowNode);
23582358
public void onAllAnimationsComplete();
23592359
public void onAnimationStarted();
23602360
public void onSurfaceStart(facebook::react::SurfaceId surfaceId);
23612361
public void onSurfaceStop(facebook::react::SurfaceId surfaceId);
2362+
public void onTransactionAvailable(facebook::react::SurfaceId surfaceId);
23622363
public void preallocateShadowView(const facebook::react::ShadowView& shadowView);
23632364
public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId);
23642365
public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType);

scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2368,12 +2368,13 @@ class facebook::react::FabricMountingManager {
23682368
public void destroyUnmountedShadowNode(const facebook::react::ShadowNodeFamily& family);
23692369
public void dispatchCommand(const facebook::react::ShadowView& shadowView, const std::string& commandName, const folly::dynamic& args);
23702370
public void drainPreallocateViewsQueue();
2371-
public void executeMount(const facebook::react::MountingTransaction& transaction);
2371+
public void executeMount(const facebook::react::MountingTransaction& transaction, bool synchronous = false);
23722372
public void maybePreallocateShadowNode(const facebook::react::ShadowNode& shadowNode);
23732373
public void onAllAnimationsComplete();
23742374
public void onAnimationStarted();
23752375
public void onSurfaceStart(facebook::react::SurfaceId surfaceId);
23762376
public void onSurfaceStop(facebook::react::SurfaceId surfaceId);
2377+
public void onTransactionAvailable(facebook::react::SurfaceId surfaceId);
23772378
public void preallocateShadowView(const facebook::react::ShadowView& shadowView);
23782379
public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId);
23792380
public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType);

0 commit comments

Comments
 (0)