From b0a91b90cec2d6dcad63a472f14a774eb6a3270c Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 14 Jan 2026 06:29:08 -0800 Subject: [PATCH 1/3] Refactor how we promote shadow tree revisions as latest for JS consistency Summary: Changelog: [General][Changed] - Refactor how shadow tree revisions are promoted as latest for JS consistency Changes `LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision` to pull the latest commited revision for a given surface id instead of accepting the new revision as a parameter. This way, the new revision is read from the same place for every update, which opens up the way to implement commit branching. This change will allow to always read from the JS tree revision, if it exists. Differential Revision: D88151490 --- .../react/renderer/uimanager/UIManager.cpp | 23 ++++++----- ...zyShadowTreeRevisionConsistencyManager.cpp | 40 ++++++++----------- ...LazyShadowTreeRevisionConsistencyManager.h | 4 +- ...adowTreeRevisionConsistencyManagerTest.cpp | 12 +++--- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 931bfce1c1e10a..74d4c89ccabd78 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -188,8 +188,10 @@ void UIManager::completeSurface( ShadowTree::CommitOptions commitOptions) { TraceSection s("UIManager::completeSurface", "surfaceId", surfaceId); + ShadowTree::CommitStatus result; + shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { - auto result = shadowTree.commit( + result = shadowTree.commit( [&](const RootShadowNode& oldRootShadowNode) { return std::make_shared( oldRootShadowNode, @@ -199,20 +201,19 @@ void UIManager::completeSurface( }); }, commitOptions); + }); - if (result == ShadowTree::CommitStatus::Succeeded) { - // It's safe to update the visible revision of the shadow tree immediately - // after we commit a specific one. - lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision( - surfaceId, shadowTree.getCurrentRevision().rootShadowNode); + if (result == ShadowTree::CommitStatus::Succeeded) { + // It's safe to update the visible revision of the shadow tree immediately + // after we commit a specific one. + lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision(surfaceId); - if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - if (auto animationBackend = animationBackend_.lock()) { - animationBackend->clearRegistry(surfaceId); - } + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + if (auto animationBackend = animationBackend_.lock()) { + animationBackend->clearRegistry(surfaceId); } } - }); + } } void UIManager::setIsJSResponder( diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp index 85cd9cd5158a23..8afaad89b39b20 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp @@ -15,22 +15,32 @@ LazyShadowTreeRevisionConsistencyManager:: ShadowTreeRegistry& shadowTreeRegistry) : shadowTreeRegistry_(shadowTreeRegistry) {} -void LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision( - SurfaceId surfaceId, - RootShadowNode::Shared rootShadowNode) { +std::shared_ptr +LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision( + SurfaceId surfaceId) { + // This method is only going to be called from JS, so we don't need to protect + // the access to the shadow tree registry as well. + // If this was multi-threaded, we would need to protect it to avoid capturing + // root shadow nodes concurrently. + RootShadowNode::Shared rootShadowNode; + shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { + rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; + }); + std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_); // We don't need to store the revision if we haven't locked. // We can resolve lazily when requested. if (lockCount > 0) { - capturedRootShadowNodesForConsistency_[surfaceId] = - std::move(rootShadowNode); + capturedRootShadowNodesForConsistency_[surfaceId] = rootShadowNode; } + + return rootShadowNode; } #pragma mark - ShadowTreeRevisionProvider -RootShadowNode::Shared +std::shared_ptr LazyShadowTreeRevisionConsistencyManager::getCurrentRevision( SurfaceId surfaceId) { { @@ -43,23 +53,7 @@ LazyShadowTreeRevisionConsistencyManager::getCurrentRevision( } } - // This method is only going to be called from JS, so we don't need to protect - // the access to the shadow tree registry as well. - // If this was multi-threaded, we would need to protect it to avoid capturing - // root shadow nodes concurrently. - RootShadowNode::Shared rootShadowNode; - shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { - rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; - }); - - { - std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_); - if (lockCount > 0) { - capturedRootShadowNodesForConsistency_[surfaceId] = rootShadowNode; - } - } - - return rootShadowNode; + return updateCurrentRevision(surfaceId); } #pragma mark - ConsistentShadowTreeRevisionProvider diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h index 96c7537ed28d2c..b63ec2977dbe34 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h @@ -28,11 +28,11 @@ class LazyShadowTreeRevisionConsistencyManager : public ShadowTreeRevisionConsis public: explicit LazyShadowTreeRevisionConsistencyManager(ShadowTreeRegistry &shadowTreeRegistry); - void updateCurrentRevision(SurfaceId surfaceId, RootShadowNode::Shared rootShadowNode); + std::shared_ptr updateCurrentRevision(SurfaceId surfaceId); #pragma mark - ShadowTreeRevisionProvider - RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override; + std::shared_ptr getCurrentRevision(SurfaceId surfaceId) override; #pragma mark - ShadowTreeRevisionConsistencyManager diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp index 01d43d096c55c8..322172b3de1db7 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp @@ -141,7 +141,7 @@ TEST_F( EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr); - consistencyManager_.updateCurrentRevision(0, newRootShadowNode); + consistencyManager_.updateCurrentRevision(0); EXPECT_NE(consistencyManager_.getCurrentRevision(0), nullptr); EXPECT_EQ( @@ -176,7 +176,7 @@ TEST_F( EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr); - consistencyManager_.updateCurrentRevision(0, newRootShadowNode); + consistencyManager_.updateCurrentRevision(0); EXPECT_EQ( consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get()); @@ -192,7 +192,7 @@ TEST_F( {}); }); - consistencyManager_.updateCurrentRevision(0, newRootShadowNode2); + consistencyManager_.updateCurrentRevision(0); EXPECT_EQ( consistencyManager_.getCurrentRevision(0).get(), @@ -265,7 +265,7 @@ TEST_F( EXPECT_EQ( consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get()); - consistencyManager_.updateCurrentRevision(0, newRootShadowNode2); + consistencyManager_.updateCurrentRevision(0); // Updated EXPECT_EQ( @@ -344,7 +344,9 @@ TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testUpdateToUnmounted) { EXPECT_EQ( consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get()); - consistencyManager_.updateCurrentRevision(0, nullptr); + shadowTreeRegistry_.remove(0); + + consistencyManager_.updateCurrentRevision(0); // Updated EXPECT_EQ(consistencyManager_.getCurrentRevision(0).get(), nullptr); From c2ca708535bcaf9168e01db1a66a8bdb7cab7511 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 14 Jan 2026 07:29:37 -0800 Subject: [PATCH 2/3] Add feature flag for Fabric commit branching Summary: Changelog: [Internal] Adds a feature flag for the Fabric commit branching mechanism. Differential Revision: D88151491 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 132 ++++++++++-------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 10 ++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 20 files changed, 192 insertions(+), 76 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 468ebfe32d13b1..a7c3a340378ed9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -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<<7a9f9d29e1d5f01df33a0893e143a6db>> + * @generated SignedSource<<1bcedffb675f2649666ec7f88752cf53>> */ /** @@ -162,6 +162,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableExclusivePropsUpdateAndroid(): Boolean = accessor.enableExclusivePropsUpdateAndroid() + /** + * Enables Fabric commit branching to fix starvation problems and atomic JS updates. + */ + @JvmStatic + public fun enableFabricCommitBranching(): Boolean = accessor.enableFabricCommitBranching() + /** * This feature flag enables logs for Fabric. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 476f2e30daf243..570adee5e20d4a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -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<> + * @generated SignedSource<> */ /** @@ -42,6 +42,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableEagerMainQueueModulesOnIOSCache: Boolean? = null private var enableEagerRootViewAttachmentCache: Boolean? = null private var enableExclusivePropsUpdateAndroidCache: Boolean? = null + private var enableFabricCommitBranchingCache: Boolean? = null private var enableFabricLogsCache: Boolean? = null private var enableFabricRendererCache: Boolean? = null private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null @@ -297,6 +298,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableFabricCommitBranching(): Boolean { + var cached = enableFabricCommitBranchingCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableFabricCommitBranching() + enableFabricCommitBranchingCache = cached + } + return cached + } + override fun enableFabricLogs(): Boolean { var cached = enableFabricLogsCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 525fb9a86b1efa..2dbe99fafa31a9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -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<> + * @generated SignedSource<> */ /** @@ -72,6 +72,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableExclusivePropsUpdateAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableFabricCommitBranching(): Boolean + @DoNotStrip @JvmStatic public external fun enableFabricLogs(): Boolean @DoNotStrip @JvmStatic public external fun enableFabricRenderer(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 4a9e42bbe3d3fc..220077657e6464 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -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<> + * @generated SignedSource<<6f46cc2bd1ea42d9e78563e61286b55c>> */ /** @@ -67,6 +67,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableExclusivePropsUpdateAndroid(): Boolean = false + override fun enableFabricCommitBranching(): Boolean = false + override fun enableFabricLogs(): Boolean = false override fun enableFabricRenderer(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 4b6b0bd24b9dd9..707d25b0fb7f39 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -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<<44f52bff0ecd0f26c2173581b09dccce>> + * @generated SignedSource<> */ /** @@ -46,6 +46,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableEagerMainQueueModulesOnIOSCache: Boolean? = null private var enableEagerRootViewAttachmentCache: Boolean? = null private var enableExclusivePropsUpdateAndroidCache: Boolean? = null + private var enableFabricCommitBranchingCache: Boolean? = null private var enableFabricLogsCache: Boolean? = null private var enableFabricRendererCache: Boolean? = null private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null @@ -323,6 +324,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableFabricCommitBranching(): Boolean { + var cached = enableFabricCommitBranchingCache + if (cached == null) { + cached = currentProvider.enableFabricCommitBranching() + accessedFeatureFlags.add("enableFabricCommitBranching") + enableFabricCommitBranchingCache = cached + } + return cached + } + override fun enableFabricLogs(): Boolean { var cached = enableFabricLogsCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 69eae3cdac01f7..13e403f16d5e3c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -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<<2bc1b7c78ced990301722e1c1dcc2dcf>> + * @generated SignedSource<> */ /** @@ -67,6 +67,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableExclusivePropsUpdateAndroid(): Boolean + @DoNotStrip public fun enableFabricCommitBranching(): Boolean + @DoNotStrip public fun enableFabricLogs(): Boolean @DoNotStrip public fun enableFabricRenderer(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 14679e254340bb..64669f578ed826 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -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<<89d63e717ae8634f32613243bcbe2c40>> + * @generated SignedSource<> */ /** @@ -171,6 +171,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableFabricCommitBranching() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableFabricCommitBranching"); + return method(javaProvider_); + } + bool enableFabricLogs() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableFabricLogs"); @@ -621,6 +627,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableExclusivePropsUpdateAndroid( return ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableFabricCommitBranching( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableFabricCommitBranching(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableFabricLogs( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableFabricLogs(); @@ -998,6 +1009,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableExclusivePropsUpdateAndroid", JReactNativeFeatureFlagsCxxInterop::enableExclusivePropsUpdateAndroid), + makeNativeMethod( + "enableFabricCommitBranching", + JReactNativeFeatureFlagsCxxInterop::enableFabricCommitBranching), makeNativeMethod( "enableFabricLogs", JReactNativeFeatureFlagsCxxInterop::enableFabricLogs), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 2e92ce6725d83b..4a0dbc06ebf074 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -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<<1cf1c6d1d2a98a495db315e0e7edcc32>> + * @generated SignedSource<<178ac10d3bb50b5265a9c876408a1ce4>> */ /** @@ -96,6 +96,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableExclusivePropsUpdateAndroid( facebook::jni::alias_ref); + static bool enableFabricCommitBranching( + facebook::jni::alias_ref); + static bool enableFabricLogs( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 1ac8889482e27c..3527da5d41e127 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -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<> + * @generated SignedSource<<3435395a9b7e728dbdf4f05cdfb1ef0c>> */ /** @@ -114,6 +114,10 @@ bool ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid() { return getAccessor().enableExclusivePropsUpdateAndroid(); } +bool ReactNativeFeatureFlags::enableFabricCommitBranching() { + return getAccessor().enableFabricCommitBranching(); +} + bool ReactNativeFeatureFlags::enableFabricLogs() { return getAccessor().enableFabricLogs(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index b61f3b259d85a9..a60ff738ce21c6 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -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<<0741bbc608bc7d7fdd242d33197057b7>> + * @generated SignedSource<<49186e1c0a2f74379bf6b0292bb9031d>> */ /** @@ -149,6 +149,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableExclusivePropsUpdateAndroid(); + /** + * Enables Fabric commit branching to fix starvation problems and atomic JS updates. + */ + RN_EXPORT static bool enableFabricCommitBranching(); + /** * This feature flag enables logs for Fabric. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index d967c9511d36f1..abd6c97d2c6199 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -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<> + * @generated SignedSource<<06632eb42486a31e016dd920f2c1a5c2>> */ /** @@ -425,6 +425,24 @@ bool ReactNativeFeatureFlagsAccessor::enableExclusivePropsUpdateAndroid() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranching() { + auto flagValue = enableFabricCommitBranching_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(22, "enableFabricCommitBranching"); + + flagValue = currentProvider_->enableFabricCommitBranching(); + enableFabricCommitBranching_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() { auto flagValue = enableFabricLogs_.load(); @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableFabricLogs"); + markFlagAsAccessed(23, "enableFabricLogs"); flagValue = currentProvider_->enableFabricLogs(); enableFabricLogs_ = flagValue; @@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricRenderer() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(23, "enableFabricRenderer"); + markFlagAsAccessed(24, "enableFabricRenderer"); flagValue = currentProvider_->enableFabricRenderer(); enableFabricRenderer_ = flagValue; @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFontScaleChangesUpdatingLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableFontScaleChangesUpdatingLayout"); + markFlagAsAccessed(25, "enableFontScaleChangesUpdatingLayout"); flagValue = currentProvider_->enableFontScaleChangesUpdatingLayout(); enableFontScaleChangesUpdatingLayout_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSTextBaselineOffsetPerLine() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(26, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(26, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(27, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(28, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingJNIBatchingAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableImagePrefetchingJNIBatchingAndroid"); + markFlagAsAccessed(29, "enableImagePrefetchingJNIBatchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingJNIBatchingAndroid(); enableImagePrefetchingJNIBatchingAndroid_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingOnUiThreadAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableImagePrefetchingOnUiThreadAndroid"); + markFlagAsAccessed(30, "enableImagePrefetchingOnUiThreadAndroid"); flagValue = currentProvider_->enableImagePrefetchingOnUiThreadAndroid(); enableImagePrefetchingOnUiThreadAndroid_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetC // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(31, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeFocus() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "enableImperativeFocus"); + markFlagAsAccessed(32, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(33, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIntersectionObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(34, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableKeyEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableKeyEvents"); + markFlagAsAccessed(35, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(36, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(37, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enableMainQueueCoordinatorOnIOS"); + markFlagAsAccessed(38, "enableMainQueueCoordinatorOnIOS"); flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS(); enableMainQueueCoordinatorOnIOS_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(39, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableNativeCSSParsing"); + markFlagAsAccessed(40, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableNetworkEventReporting"); + markFlagAsAccessed(41, "enableNetworkEventReporting"); flagValue = currentProvider_->enableNetworkEventReporting(); enableNetworkEventReporting_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enablePreparedTextLayout"); + markFlagAsAccessed(42, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(43, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSwiftUIBasedFilters() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(44, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enableViewCulling"); + markFlagAsAccessed(45, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "enableViewRecycling"); + markFlagAsAccessed(46, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForImage() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "enableViewRecyclingForImage"); + markFlagAsAccessed(47, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(48, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "enableViewRecyclingForText"); + markFlagAsAccessed(49, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "enableViewRecyclingForView"); + markFlagAsAccessed(50, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewContainerStateExperimenta // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(51, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(52, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(53, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::fixTextClippingAndroid15useBoundsForWidth( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "fixTextClippingAndroid15useBoundsForWidth"); + markFlagAsAccessed(54, "fixTextClippingAndroid15useBoundsForWidth"); flagValue = currentProvider_->fixTextClippingAndroid15useBoundsForWidth(); fixTextClippingAndroid15useBoundsForWidth_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxAssertSingleHostState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(55, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "fuseboxEnabledRelease"); + markFlagAsAccessed(56, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(57, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(58, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(59, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "perfIssuesEnabled"); + markFlagAsAccessed(60, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "perfMonitorV2Enabled"); + markFlagAsAccessed(61, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1136,7 +1154,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "preparedTextCacheSize"); + markFlagAsAccessed(62, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(63, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(64, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldTriggerResponderTransferOnScrollAndr // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(65, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(66, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(67, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(68, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(69, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "useFabricInterop"); + markFlagAsAccessed(70, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(71, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "useShadowNodeStateOnClone"); + markFlagAsAccessed(72, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "useSharedAnimatedBackend"); + markFlagAsAccessed(73, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(74, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "useTurboModuleInterop"); + markFlagAsAccessed(75, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "useTurboModules"); + markFlagAsAccessed(76, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1406,7 +1424,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "viewCullingOutsetRatio"); + markFlagAsAccessed(77, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1424,7 +1442,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(77, "virtualViewPrerenderRatio"); + markFlagAsAccessed(78, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 277146234d1ff7..eb3b50cac48f3b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -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<<0a3237b70685886405ea47d3a8c83280>> + * @generated SignedSource<<1379ac6607444dbde65b113221edaf27>> */ /** @@ -54,6 +54,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableEagerMainQueueModulesOnIOS(); bool enableEagerRootViewAttachment(); bool enableExclusivePropsUpdateAndroid(); + bool enableFabricCommitBranching(); bool enableFabricLogs(); bool enableFabricRenderer(); bool enableFontScaleChangesUpdatingLayout(); @@ -121,7 +122,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 78> accessedFeatureFlags_; + std::array, 79> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -145,6 +146,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableEagerMainQueueModulesOnIOS_; std::atomic> enableEagerRootViewAttachment_; std::atomic> enableExclusivePropsUpdateAndroid_; + std::atomic> enableFabricCommitBranching_; std::atomic> enableFabricLogs_; std::atomic> enableFabricRenderer_; std::atomic> enableFontScaleChangesUpdatingLayout_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index a657551f6227f6..4ea65ba857620b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -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<<4d8ad520eb6442e7d68b739889cc57ce>> + * @generated SignedSource<<8951c3a0a96fb3b725bd1ff2de7789e9>> */ /** @@ -115,6 +115,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableFabricCommitBranching() override { + return false; + } + bool enableFabricLogs() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index b811f17a2b2409..7431df64f42018 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -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<<5d268976bbf89c50ed0fac1913925803>> + * @generated SignedSource<<7f558deb28dc82b25567426240b2e6d5>> */ /** @@ -243,6 +243,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableExclusivePropsUpdateAndroid(); } + bool enableFabricCommitBranching() override { + auto value = values_["enableFabricCommitBranching"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableFabricCommitBranching(); + } + bool enableFabricLogs() override { auto value = values_["enableFabricLogs"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 41c360d3e10ca1..774920072a4217 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -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<> + * @generated SignedSource<<91470639697953f6ab45237b1bb72090>> */ /** @@ -47,6 +47,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableEagerMainQueueModulesOnIOS() = 0; virtual bool enableEagerRootViewAttachment() = 0; virtual bool enableExclusivePropsUpdateAndroid() = 0; + virtual bool enableFabricCommitBranching() = 0; virtual bool enableFabricLogs() = 0; virtual bool enableFabricRenderer() = 0; virtual bool enableFontScaleChangesUpdatingLayout() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 89413bab54e27e..9ee35de9c249cf 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -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<<3d7f32a7be7944a149b788ffe38f89d1>> + * @generated SignedSource<> */ /** @@ -154,6 +154,11 @@ bool NativeReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid( return ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid(); } +bool NativeReactNativeFeatureFlags::enableFabricCommitBranching( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableFabricCommitBranching(); +} + bool NativeReactNativeFeatureFlags::enableFabricLogs( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableFabricLogs(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index e75ea83757b696..5c4232ccaef764 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -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<<0fdc53b52abd242240617cabe47477b3>> + * @generated SignedSource<<1fc9204eab22c1eba27a3dd90f14c2a3>> */ /** @@ -80,6 +80,8 @@ class NativeReactNativeFeatureFlags bool enableExclusivePropsUpdateAndroid(jsi::Runtime& runtime); + bool enableFabricCommitBranching(jsi::Runtime& runtime); + bool enableFabricLogs(jsi::Runtime& runtime); bool enableFabricRenderer(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 501e5c2197b669..03695ea39e506b 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -286,6 +286,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableFabricCommitBranching: { + defaultValue: false, + metadata: { + description: + 'Enables Fabric commit branching to fix starvation problems and atomic JS updates.', + expectedReleaseValue: true, + purpose: 'release', + }, + ossReleaseStage: 'none', + }, enableFabricLogs: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index be04495dc73b62..2671948a34549c 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -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<<0acb1755465fae151917d6043a737331>> + * @generated SignedSource<<78d732e45f4de66ed5313fc925783022>> * @flow strict * @noformat */ @@ -69,6 +69,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableEagerMainQueueModulesOnIOS: Getter, enableEagerRootViewAttachment: Getter, enableExclusivePropsUpdateAndroid: Getter, + enableFabricCommitBranching: Getter, enableFabricLogs: Getter, enableFabricRenderer: Getter, enableFontScaleChangesUpdatingLayout: Getter, @@ -279,6 +280,10 @@ export const enableEagerRootViewAttachment: Getter = createNativeFlagGe * When enabled, Android will disable Props 1.5 raw value merging when Props 2.0 is available. */ export const enableExclusivePropsUpdateAndroid: Getter = createNativeFlagGetter('enableExclusivePropsUpdateAndroid', false); +/** + * Enables Fabric commit branching to fix starvation problems and atomic JS updates. + */ +export const enableFabricCommitBranching: Getter = createNativeFlagGetter('enableFabricCommitBranching', false); /** * This feature flag enables logs for Fabric. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index b38207f4a883cb..0ee31f6b7d0aa8 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -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<<3540142c0e0e90aaffeb7ea02812e7b1>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -47,6 +47,7 @@ export interface Spec extends TurboModule { +enableEagerMainQueueModulesOnIOS?: () => boolean; +enableEagerRootViewAttachment?: () => boolean; +enableExclusivePropsUpdateAndroid?: () => boolean; + +enableFabricCommitBranching?: () => boolean; +enableFabricLogs?: () => boolean; +enableFabricRenderer?: () => boolean; +enableFontScaleChangesUpdatingLayout?: () => boolean; From 98b32145dfd81a892f5fa25cfbcc88d2fefe2b21 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 14 Jan 2026 07:33:13 -0800 Subject: [PATCH 3/3] Implement Fabric commit branching (behind a flag) (#54835) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/54835 Changelog: [General][Changed] - Changed how React changes are commited to the Shadow Tree (behind a feature flag) Introduces two branches for the Shadow Tree commits - React always commits to the JS revision, while other sources should commit to the main revision. Commits to the JS revision schedule a "merge" commit at the end of the event loop pass to upstream the React changes to the main revision. "Merge" means taking the JS revision and promoting it to become the new main, losing changes that happened between fork and merge. Preservation of these changes is the responsibility of the party that created them, and they should be reapplied using a commit hook. Reviewed By: rubennorte Differential Revision: D88151489 --- .../react/renderer/mounting/ShadowTree.cpp | 89 ++++++++++++++++--- .../react/renderer/mounting/ShadowTree.h | 26 +++++- .../renderer/mounting/ShadowTreeDelegate.h | 5 ++ .../tests/StateReconciliationTest.cpp | 3 + .../react/renderer/scheduler/Scheduler.cpp | 12 +++ .../react/renderer/scheduler/Scheduler.h | 1 + .../react/renderer/uimanager/UIManager.cpp | 7 ++ .../react/renderer/uimanager/UIManager.h | 2 + .../renderer/uimanager/UIManagerDelegate.h | 5 ++ ...zyShadowTreeRevisionConsistencyManager.cpp | 4 +- ...LazyShadowTreeRevisionConsistencyManager.h | 1 - ...adowTreeRevisionConsistencyManagerTest.cpp | 3 + .../react/runtime/ReactHost.cpp | 3 + 13 files changed, 148 insertions(+), 13 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index 58c5bbaa4e0844..a4c4ca0f234a56 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -286,18 +286,32 @@ CommitStatus ShadowTree::tryCommit( const CommitOptions& commitOptions) const { TraceSection s("ShadowTree::commit"); + auto isReactBranch = ReactNativeFeatureFlags::enableFabricCommitBranching() && + commitOptions.source == CommitSource::React; + + // Commits on the JS branch are never synchronous. + react_native_assert(!isReactBranch || !commitOptions.mountSynchronously); + auto telemetry = TransactionTelemetry{}; telemetry.willCommit(); CommitMode commitMode; auto oldRevision = ShadowTreeRevision{}; + auto oldRevisionForStateProgression = ShadowTreeRevision{}; auto newRevision = ShadowTreeRevision{}; { // Reading `currentRevision_` in shared manner. SharedLock lock = sharedCommitLock(); commitMode = commitMode_; - oldRevision = currentRevision_; + + if (isReactBranch && currentReactRevision_.has_value()) { + oldRevision = currentReactRevision_.value(); + } else { + oldRevision = currentRevision_; + } + + oldRevisionForStateProgression = currentRevision_; } const auto& oldRootShadowNode = oldRevision.rootShadowNode; @@ -308,8 +322,8 @@ CommitStatus ShadowTree::tryCommit( } if (commitOptions.enableStateReconciliation) { - auto updatedNewRootShadowNode = - progressState(*newRootShadowNode, *oldRootShadowNode); + auto updatedNewRootShadowNode = progressState( + *newRootShadowNode, *oldRevisionForStateProgression.rootShadowNode); if (updatedNewRootShadowNode) { newRootShadowNode = std::static_pointer_cast(updatedNewRootShadowNode); @@ -336,9 +350,10 @@ CommitStatus ShadowTree::tryCommit( { // Updating `currentRevision_` in unique manner if it hasn't changed. - UniqueLock lock = uniqueCommitLock(); + UniqueLock lock = uniqueCommitLock( + /*defer*/ isReactBranch); - if (currentRevision_.number != oldRevision.number) { + if (!isReactBranch && currentRevision_.number != oldRevision.number) { return CommitStatus::Failed; } @@ -364,12 +379,19 @@ CommitStatus ShadowTree::tryCommit( .number = newRevisionNumber, .telemetry = telemetry}; - currentRevision_ = newRevision; + if (isReactBranch) { + auto promotionLock = std::unique_lock{promotedRevisionMutex_}; + currentReactRevision_ = newRevision; + } else { + currentRevision_ = newRevision; + } } emitLayoutEvents(affectedLayoutableNodes); - if (commitMode == CommitMode::Normal) { + if (isReactBranch) { + scheduleReactRevisionPromotion(); + } else if (commitMode == CommitMode::Normal) { mount(std::move(newRevision), commitOptions.mountSynchronously); } @@ -381,6 +403,10 @@ ShadowTreeRevision ShadowTree::getCurrentRevision() const { return currentRevision_; } +std::optional ShadowTree::getCurrentReactRevision() const { + return currentReactRevision_; +} + void ShadowTree::mount(ShadowTreeRevision revision, bool mountSynchronously) const { mountingCoordinator_->push(std::move(revision)); @@ -388,6 +414,47 @@ void ShadowTree::mount(ShadowTreeRevision revision, bool mountSynchronously) mountingCoordinator_, mountSynchronously); } +void ShadowTree::mergeReactRevision() const { + auto lock = std::unique_lock{promotedRevisionMutex_}; + + if (!reactRevisionToBePromoted_.has_value()) { + return; + } + + { + UniqueLock commitLock = uniqueCommitLock( + /*defer*/ false); + + if (currentReactRevision_.has_value() && + reactRevisionToBePromoted_.value().number == + currentReactRevision_.value().number) { + currentReactRevision_.reset(); + } + } + + this->commit( + [revision = + std::exchange(reactRevisionToBePromoted_, std::nullopt).value()]( + const RootShadowNode& /*oldRootShadowNode*/) { + return std::make_shared( + *revision.rootShadowNode, ShadowNodeFragment{}); + }, + { + .enableStateReconciliation = true, + .mountSynchronously = true, + .source = CommitSource::ReactRevisionMerge, + }); +} + +void ShadowTree::promoteReactRevision() const { + auto lock = std::unique_lock{promotedRevisionMutex_}; + reactRevisionToBePromoted_ = currentReactRevision_.value(); +} + +void ShadowTree::scheduleReactRevisionPromotion() const { + delegate_.shadowTreeDidFinishReactCommit(*this); +} + void ShadowTree::commitEmptyTree() const { commit( [](const RootShadowNode& oldRootShadowNode) @@ -426,11 +493,13 @@ void ShadowTree::notifyDelegatesOfUpdates() const { delegate_.shadowTreeDidFinishTransaction(mountingCoordinator_, true); } -inline ShadowTree::UniqueLock ShadowTree::uniqueCommitLock() const { +inline ShadowTree::UniqueLock ShadowTree::uniqueCommitLock(bool defer) const { if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { - return std::unique_lock{commitMutexRecursive_}; + return defer ? std::unique_lock{commitMutexRecursive_, std::defer_lock} + : std::unique_lock{commitMutexRecursive_}; } else { - return std::unique_lock{commitMutex_}; + return defer ? std::unique_lock{commitMutex_, std::defer_lock} + : std::unique_lock{commitMutex_}; } } diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index c1f51fc1a91e82..c8672d533bf114 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -51,6 +51,7 @@ enum class ShadowTreeCommitSource { Unknown, React, AnimationEndSync, + ReactRevisionMerge, }; struct ShadowTreeCommitOptions { @@ -124,6 +125,12 @@ class ShadowTree final { */ ShadowTreeRevision getCurrentRevision() const; + /* + * Returns a `ShadowTreeRevision` representing the momentary state of + * the `ShadowTree` in the JS thread. + */ + std::optional getCurrentReactRevision() const; + /* * Commit an empty tree (a new `RootShadowNode` with no children). */ @@ -137,6 +144,18 @@ class ShadowTree final { std::shared_ptr getMountingCoordinator() const; + /** + * Promotes the current React revision to be merged into the main branch of the + * ShadowTree. + */ + void promoteReactRevision() const; + + /** + * Commits the currently promoted React revision to the "main" branch of the + * ShadowTree. No-op if the promoted React revision doesn't exist. + */ + void mergeReactRevision() const; + private: constexpr static ShadowTreeRevision::Number INITIAL_REVISION{0}; @@ -144,18 +163,23 @@ class ShadowTree final { void emitLayoutEvents(std::vector &affectedLayoutableNodes) const; + void scheduleReactRevisionPromotion() const; + const SurfaceId surfaceId_; const ShadowTreeDelegate &delegate_; mutable std::shared_mutex commitMutex_; + mutable std::shared_mutex promotedRevisionMutex_; mutable std::recursive_mutex commitMutexRecursive_; mutable CommitMode commitMode_{CommitMode::Normal}; // Protected by `commitMutex_`. mutable ShadowTreeRevision currentRevision_; // Protected by `commitMutex_`. + mutable std::optional currentReactRevision_; // Only updated from JS thread. + mutable std::optional reactRevisionToBePromoted_; // Protected by `promotedRevisionMutex_`. std::shared_ptr mountingCoordinator_; using UniqueLock = std::variant, std::unique_lock>; using SharedLock = std::variant, std::unique_lock>; - inline UniqueLock uniqueCommitLock() const; + inline UniqueLock uniqueCommitLock(bool defer = false) const; inline SharedLock sharedCommitLock() const; }; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h index 650d898a6f3453..40e610911a28e8 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h @@ -38,6 +38,11 @@ class ShadowTreeDelegate { std::shared_ptr mountingCoordinator, bool mountSynchronously) const = 0; + /* + * Called right after Shadow Tree commits a new JS revision of the tree. + */ + virtual void shadowTreeDidFinishReactCommit(const ShadowTree &shadowTree) const = 0; + virtual ~ShadowTreeDelegate() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp index 7290d76cc637cb..a9c553e4f84fd9 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp @@ -37,6 +37,9 @@ class DummyShadowTreeDelegate : public ShadowTreeDelegate { void shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, bool /*mountSynchronously*/) const override {} + + void shadowTreeDidFinishReactCommit( + const ShadowTree& /*shadowTree*/) const override {} }; namespace { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 8c42afc52964cd..39513698234e0f 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -355,6 +355,18 @@ void Scheduler::uiManagerShouldRemoveEventListener( removeEventListener(listener); } +void Scheduler::uiManagerDidFinishJSCommit(const ShadowTree& shadowTree) { + auto surfaceId = shadowTree.getSurfaceId(); + runtimeScheduler_->scheduleRenderingUpdate( + surfaceId, [surfaceId, uiManager = uiManager_]() { + uiManager->getShadowTreeRegistry().visit( + surfaceId, [](const ShadowTree& tree) { + tree.promoteReactRevision(); + tree.mergeReactRevision(); + }); + }); +} + void Scheduler::uiManagerDidStartSurface(const ShadowTree& shadowTree) { std::shared_lock lock(onSurfaceStartCallbackMutex_); if (onSurfaceStartCallback_) { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index c324655da7255a..611faa69eca34e 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -97,6 +97,7 @@ class Scheduler final : public UIManagerDelegate { void uiManagerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void uiManagerShouldAddEventListener(std::shared_ptr listener) final; void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) final; + void uiManagerDidFinishJSCommit(const ShadowTree &shadowTree) override; void uiManagerDidStartSurface(const ShadowTree &shadowTree) override; #pragma mark - ContextContainer diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 74d4c89ccabd78..f7b2f46fd8646d 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -646,6 +646,13 @@ void UIManager::shadowTreeDidFinishTransaction( } } +void UIManager::shadowTreeDidFinishReactCommit( + const ShadowTree& shadowTree) const { + if (delegate_ != nullptr) { + delegate_->uiManagerDidFinishJSCommit(shadowTree); + } +} + void UIManager::reportMount(SurfaceId surfaceId) const { TraceSection s("UIManager::reportMount"); diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 794a5559a553fe..e61ed6c6c2b718 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -135,6 +135,8 @@ class UIManager final : public ShadowTreeDelegate { const RootShadowNode::Unshared &newRootShadowNode, const ShadowTree::CommitOptions &commitOptions) const override; + void shadowTreeDidFinishReactCommit(const ShadowTree &shadowTree) const override; + std::shared_ptr createNode( Tag tag, const std::string &componentName, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 32df5c92f10992..13d71d9d2859c5 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -84,6 +84,11 @@ class UIManagerDelegate { */ virtual void uiManagerDidStartSurface(const ShadowTree &shadowTree) = 0; + /* + * Called after a new JS revision of the shadow tree is committed. + */ + virtual void uiManagerDidFinishJSCommit(const ShadowTree &shadowTree) = 0; + using OnSurfaceStartCallback = std::function; virtual void uiManagerShouldSetOnSurfaceStartCallback(OnSurfaceStartCallback &&callback) = 0; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp index 8afaad89b39b20..cf11c7f9d28e64 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp @@ -24,7 +24,9 @@ LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision( // root shadow nodes concurrently. RootShadowNode::Shared rootShadowNode; shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { - rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; + auto reactRevision = shadowTree.getCurrentReactRevision(); + rootShadowNode = + reactRevision.value_or(shadowTree.getCurrentRevision()).rootShadowNode; }); std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_); diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h index b63ec2977dbe34..c7ab5af7fac2ad 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h @@ -13,7 +13,6 @@ #include #include #include -#include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp index 322172b3de1db7..d17ce99c0c17c1 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/tests/LazyShadowTreeRevisionConsistencyManagerTest.cpp @@ -27,6 +27,9 @@ class FakeShadowTreeDelegate : public ShadowTreeDelegate { void shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, bool /*mountSynchronously*/) const override {} + + void shadowTreeDidFinishReactCommit( + const ShadowTree& /*shadowTree*/) const override {} }; class LazyShadowTreeRevisionConsistencyManagerTest : public ::testing::Test { diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index ca1f38300f3824..20384946fafa55 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp @@ -305,6 +305,9 @@ void ReactHost::destroyReactInstance() { reactInstanceData_->contextContainer->erase(RuntimeSchedulerKey); reactInstanceData_->mountingManager->setSchedulerTaskExecutor(nullptr); + reactInstanceData_->mountingManager = nullptr; + reactInstanceData_->contextContainer = nullptr; + reactInstanceData_->turboModuleProviders.clear(); reactInstance_ = nullptr; reactInstanceData_->messageQueueThread = nullptr; }