Skip to content

Commit 27e92a7

Browse files
synchronous updateState on android
1 parent 91e3f77 commit 27e92a7

File tree

7 files changed

+46
-23
lines changed

7 files changed

+46
-23
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.facebook.react.bridge.ReadableNativeMap
1616
import com.facebook.react.bridge.WritableMap
1717
import com.facebook.react.common.mapbuffer.ReadableMapBuffer
1818
import com.facebook.react.uimanager.ReferenceStateWrapper
19+
import com.facebook.react.uimanager.StateWrapper
1920

2021
/**
2122
* This class holds reference to the C++ EventEmitter object. Instances of this class are created on
@@ -33,7 +34,7 @@ internal class StateWrapperImpl private constructor() : HybridClassBase(), Refer
3334

3435
private external fun getStateDataReferenceImpl(): Any?
3536

36-
public external fun updateStateImpl(map: NativeMap)
37+
public external fun updateStateImpl(map: NativeMap, updateMode: Int)
3738

3839
public override val stateDataMapBuffer: ReadableMapBuffer?
3940
get() {
@@ -66,12 +67,12 @@ internal class StateWrapperImpl private constructor() : HybridClassBase(), Refer
6667
initHybrid()
6768
}
6869

69-
override fun updateState(map: WritableMap) {
70+
override fun updateState(map: WritableMap, updateMode: StateWrapper.UpdateMode) {
7071
if (!isValid) {
7172
FLog.e(TAG, "Race between StateWrapperImpl destruction and updateState")
7273
return
7374
}
74-
updateStateImpl(map as NativeMap)
75+
updateStateImpl(map as NativeMap, updateMode.value)
7576
}
7677

7778
override fun destroyState() {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ internal class MountItemDispatcher(
3535
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()
3636

3737
private var inDispatch: Boolean = false
38+
private var followUpDispatchRequired: Boolean = false
3839
var batchedExecutionTime: Long = 0L
3940
private set
4041

@@ -78,24 +79,27 @@ internal class MountItemDispatcher(
7879
@UiThread
7980
@ThreadConfined(UI)
8081
fun tryDispatchMountItems() {
81-
// If we're already dispatching, don't reenter.
82-
// Reentrance can potentially happen a lot on Android in Fabric because `updateState` from the
83-
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure we
84-
// don't reenter in those cases, but 2) still execute those queued instructions synchronously.
85-
// This is a pretty blunt tool, but we might not have better options since we really don't want
86-
// to execute anything out-of-order.
82+
// If we're already dispatching, don't reenter but signal that a follow-up dispatch is
83+
// needed. This follows the same pattern as iOS's RCTMountingManager::initiateTransaction,
84+
// which uses a do-while loop with a _followUpTransactionRequired flag to ensure mount items
85+
// enqueued during dispatch (e.g., from synchronous state updates triggered by view layout)
86+
// are processed in the same frame rather than deferred to the next one.
8787
if (inDispatch) {
88+
followUpDispatchRequired = true
8889
return
8990
}
9091

91-
inDispatch = true
92+
do {
93+
followUpDispatchRequired = false
94+
inDispatch = true
9295

93-
try {
94-
dispatchMountItems()
95-
} finally {
96-
// Clean up after running dispatchMountItems - even if an exception was thrown
97-
inDispatch = false
98-
}
96+
try {
97+
dispatchMountItems()
98+
} finally {
99+
// Clean up after running dispatchMountItems - even if an exception was thrown
100+
inDispatch = false
101+
}
102+
} while (followUpDispatchRequired)
99103

100104
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
101105
// NativeAnimatedModule relies on this for executing any animations that may have been

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ import com.facebook.react.common.mapbuffer.ReadableMapBuffer
1717
* by calling updateState, which communicates state back to the C++ layer.
1818
*/
1919
public interface StateWrapper {
20+
/**
21+
* Maps to EventQueue::UpdateMode in C++.
22+
* Controls how state updates are flushed (Async or Sync).
23+
*/
24+
public enum class UpdateMode(public val value: Int) {
25+
Asynchronous(0),
26+
unstable_Immediate(1)
27+
}
28+
2029
/**
2130
* Get a ReadableMapBuffer object from the C++ layer, which is a K/V map of short keys to values.
2231
*
@@ -32,8 +41,9 @@ public interface StateWrapper {
3241
/**
3342
* Pass a map of values back to the C++ layer. The operation is performed synchronously and cannot
3443
* fail.
44+
* updateMode controls whether the update is queued asynchronously or flushed immediately.
3545
*/
36-
public fun updateState(map: WritableMap)
46+
public fun updateState(map: WritableMap, updateMode: UpdateMode = UpdateMode.Asynchronous)
3747

3848
/**
3949
* Mark state as unused and clean up in Java and in native. This should be called as early as

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <fbjni/fbjni.h>
1010
#include <react/featureflags/ReactNativeFeatureFlags.h>
1111
#include <react/jni/ReadableNativeMap.h>
12+
#include <react/renderer/core/EventQueue.h>
1213
#include <react/renderer/mapbuffer/MapBuffer.h>
1314
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
1415

@@ -50,12 +51,14 @@ jni::local_ref<jobject> StateWrapperImpl::getStateDataReferenceImpl() {
5051
return nullptr;
5152
}
5253

53-
void StateWrapperImpl::updateStateImpl(NativeMap* map) {
54+
void StateWrapperImpl::updateStateImpl(NativeMap* map, jint updateMode) {
5455
if (state_) {
5556
// Get folly::dynamic from map
5657
auto dynamicMap = map->consume();
5758
// Set state
58-
state_->updateState(std::move(dynamicMap));
59+
state_->updateState(
60+
std::move(dynamicMap),
61+
static_cast<EventQueue::UpdateMode>(updateMode));
5962
}
6063
}
6164

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class StateWrapperImpl : public jni::HybridClass<StateWrapperImpl, StateWrapper>
2727
jni::local_ref<JReadableMapBuffer::jhybridobject> getStateMapBufferDataImpl();
2828
jni::local_ref<ReadableNativeMap::jhybridobject> getStateDataImpl();
2929
jni::local_ref<jobject> getStateDataReferenceImpl();
30-
void updateStateImpl(NativeMap *map);
30+
void updateStateImpl(NativeMap *map, jint updateMode);
3131
void setState(std::shared_ptr<const State> state);
3232
std::shared_ptr<const State> getState() const;
3333

packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ class ConcreteState : public State {
116116
return getData().getDynamic();
117117
}
118118

119-
void updateState(folly::dynamic &&data) const override
119+
void updateState(
120+
folly::dynamic &&data,
121+
EventQueue::UpdateMode updateMode = EventQueue::UpdateMode::Asynchronous) const override
120122
{
121-
updateState(Data(getData(), std::move(data)));
123+
updateState(Data(getData(), std::move(data)), updateMode);
122124
}
123125

124126
MapBuffer getMapBuffer() const override

packages/react-native/ReactCommon/react/renderer/core/State.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifdef RN_SERIALIZABLE_STATE
1111
#include <fbjni/fbjni.h>
1212
#include <folly/dynamic.h>
13+
#include <react/renderer/core/EventQueue.h>
1314
#include <react/renderer/mapbuffer/MapBuffer.h>
1415
#endif
1516

@@ -66,7 +67,9 @@ class State {
6667
virtual folly::dynamic getDynamic() const = 0;
6768
virtual MapBuffer getMapBuffer() const = 0;
6869
virtual jni::local_ref<jobject> getJNIReference() const = 0;
69-
virtual void updateState(folly::dynamic &&data) const = 0;
70+
virtual void updateState(
71+
folly::dynamic &&data,
72+
EventQueue::UpdateMode updateMode = EventQueue::UpdateMode::Asynchronous) const = 0;
7073
#endif
7174

7275
protected:

0 commit comments

Comments
 (0)