diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index cd7cea79ce1..389ad67350e 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "ShadowTreeDelegate.h" @@ -249,6 +250,8 @@ ShadowTree::ShadowTree( currentRevision_ = ShadowTreeRevision{ rootShadowNode, INITIAL_REVISION, TransactionTelemetry{}}; + lastRevisionNumberWithNewState_ = currentRevision_.number; + mountingCoordinator_ = std::make_shared(currentRevision_); } @@ -322,12 +325,14 @@ CommitStatus ShadowTree::tryCommit( CommitMode commitMode; auto oldRevision = ShadowTreeRevision{}; auto newRevision = ShadowTreeRevision{}; + ShadowTreeRevision::Number lastRevisionNumberWithNewState; { // Reading `currentRevision_` in shared manner. std::shared_lock lock(commitMutex_); commitMode = commitMode_; oldRevision = currentRevision_; + lastRevisionNumberWithNewState = lastRevisionNumberWithNewState_; } auto const &oldRootShadowNode = oldRevision.rootShadowNode; @@ -377,11 +382,21 @@ CommitStatus ShadowTree::tryCommit( return CommitStatus::Cancelled; } - if (currentRevision_.number != oldRevision.number) { - return CommitStatus::Failed; + if (CoreFeatures::enableGranularShadowTreeStateReconciliation) { + auto lastRevisionNumberWithNewStateChanged = + lastRevisionNumberWithNewState != lastRevisionNumberWithNewState_; + // Commit should only fail if we propagated the wrong state. + if (commitOptions.enableStateReconciliation && + lastRevisionNumberWithNewStateChanged) { + return CommitStatus::Failed; + } + } else { + if (currentRevision_.number != oldRevision.number) { + return CommitStatus::Failed; + } } - auto newRevisionNumber = oldRevision.number + 1; + auto newRevisionNumber = currentRevision_.number + 1; { std::scoped_lock dispatchLock(EventEmitter::DispatchMutex()); @@ -398,6 +413,9 @@ CommitStatus ShadowTree::tryCommit( std::move(newRootShadowNode), newRevisionNumber, telemetry}; currentRevision_ = newRevision; + if (!commitOptions.enableStateReconciliation) { + lastRevisionNumberWithNewState_ = newRevisionNumber; + } } emitLayoutEvents(affectedLayoutableNodes); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index 6358f2e80b7..e86821dc47a 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -55,6 +55,9 @@ class ShadowTree final { }; struct CommitOptions { + // When set to true, Shadow Node state from current revision will be applied + // to the new revision. For more details see + // https://reactnative.dev/architecture/render-pipeline#react-native-renderer-state-updates bool enableStateReconciliation{false}; // Indicates if mounting will be triggered synchronously and React will @@ -144,6 +147,8 @@ class ShadowTree final { mutable CommitMode commitMode_{ CommitMode::Normal}; // Protected by `commitMutex_`. mutable ShadowTreeRevision currentRevision_; // Protected by `commitMutex_`. + mutable ShadowTreeRevision::Number + lastRevisionNumberWithNewState_; // Protected by `commitMutex_`. MountingCoordinator::Shared mountingCoordinator_; }; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 990912be9cb..dc09ecb78b6 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -134,6 +134,10 @@ Scheduler::Scheduler( CoreFeatures::cacheLastTextMeasurement = reactNativeConfig_->getBool("react_fabric:enable_text_measure_cache"); + CoreFeatures::enableGranularShadowTreeStateReconciliation = + reactNativeConfig_->getBool( + "react_fabric:enable_granular_shadow_tree_state_reconciliation"); + if (animationDelegate != nullptr) { animationDelegate->setComponentDescriptorRegistry( componentDescriptorRegistry_); diff --git a/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp b/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp index 0554485f543..70054a836ba 100644 --- a/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp +++ b/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp @@ -20,5 +20,6 @@ bool CoreFeatures::enableMountHooks = false; bool CoreFeatures::doNotSwapLeftAndRightOnAndroidInLTR = false; bool CoreFeatures::enableCleanParagraphYogaNode = false; bool CoreFeatures::disableScrollEventThrottleRequirement = false; +bool CoreFeatures::enableGranularShadowTreeStateReconciliation = false; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/utils/CoreFeatures.h b/packages/react-native/ReactCommon/react/utils/CoreFeatures.h index 3c9af96e609..3a3763976ec 100644 --- a/packages/react-native/ReactCommon/react/utils/CoreFeatures.h +++ b/packages/react-native/ReactCommon/react/utils/CoreFeatures.h @@ -60,6 +60,10 @@ class CoreFeatures { // Fire `onScroll` events continuously on iOS without a `scrollEventThrottle` // props, and provide continuous `onScroll` upates like other platforms. static bool disableScrollEventThrottleRequirement; + + // When enabled, the renderer would only fail commits when they propagate + // state and the last commit that updated state changed before committing. + static bool enableGranularShadowTreeStateReconciliation; }; } // namespace facebook::react