diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index a20d9cdb0a90..b8135a0ceda9 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.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. * - * @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:* optimizedAnimatedPropUpdates:* + * @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:* optimizedAnimatedPropUpdates:* enableCxxAnimatedDirectPathInBackend:* * @flow strict-local * @format */ diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js new file mode 100644 index 000000000000..a4be1b984c94 --- /dev/null +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @fantom_flags useSharedAnimatedBackend:true enableCxxAnimatedDirectPathInBackend:* updateRuntimeShadowNodeReferencesOnCommitThread:* + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import type {HostInstance} from 'react-native'; + +import * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {createRef, useState} from 'react'; +import {Animated, View, useAnimatedValue} from 'react-native'; + +let _opacity; +let _setAnimatedWidth; + +// `still` has the same position and structure as `animated` but never +// animates, so it is cloned only for the reasons unrelated to animations. +function MyApp({ + animatedRef, + stillRef, +}: { + animatedRef: React.RefSetter, + stillRef?: React.RefSetter, +}) { + const opacity = useAnimatedValue(1); + const stillOpacity = useAnimatedValue(1); + const [animatedWidth, setAnimatedWidth] = useState(100); + _opacity = opacity; + _setAnimatedWidth = setAnimatedWidth; + return ( + + + + + + ); +} + +function startOpacityAnimation(duration: number) { + Fantom.runTask(() => { + Animated.timing(_opacity, { + toValue: 0, + duration, + useNativeDriver: true, + }).start(); + }); +} + +test('a React commit of the animated view keeps its direct animated value mounted', () => { + const animatedRef = createRef(); + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + startOpacityAnimation(1000); + Fantom.unstable_produceFramesForDuration(500); + + Fantom.runTask(() => { + _setAnimatedWidth(150); + }); + + expect(root.getRenderedOutput({props: ['opacity', 'width']}).toJSX()).toEqual( + + + + + , + ); +}); 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 7fcdaaeec16d..644d173fbe29 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<> + * @generated SignedSource<> */ /** @@ -156,6 +156,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean = accessor.enableCustomFocusSearchOnClippedElementsAndroid() + /** + * With the shared animation backend, C++ Animated keeps its non-layout values out of React commits and commits the final values once per surface when animations end, as it does without the backend. + */ + @JvmStatic + public fun enableCxxAnimatedDirectPathInBackend(): Boolean = accessor.enableCxxAnimatedDirectPathInBackend() + /** * Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work. */ 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 6e0da4d9d0e4..d7d987e4f75e 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<> */ /** @@ -41,6 +41,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableBridgelessArchitectureCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null + private var enableCxxAnimatedDirectPathInBackendCache: Boolean? = null private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null private var enableDoubleMeasurementFixAndroidCache: Boolean? = null private var enableEagerRootViewAttachmentCache: Boolean? = null @@ -307,6 +308,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableCxxAnimatedDirectPathInBackend(): Boolean { + var cached = enableCxxAnimatedDirectPathInBackendCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableCxxAnimatedDirectPathInBackend() + enableCxxAnimatedDirectPathInBackendCache = cached + } + return cached + } + override fun enableDestroyShadowTreeRevisionAsync(): Boolean { var cached = enableDestroyShadowTreeRevisionAsyncCache 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 2268d03f3674..8ef5198fce0b 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<<6d86d4adbc261c9271b5bed72bd12dcf>> + * @generated SignedSource<> */ /** @@ -70,6 +70,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableCxxAnimatedDirectPathInBackend(): Boolean + @DoNotStrip @JvmStatic public external fun enableDestroyShadowTreeRevisionAsync(): Boolean @DoNotStrip @JvmStatic public external fun enableDoubleMeasurementFixAndroid(): 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 defafe46f6b4..abf8a46c5757 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<<28e566c4e784a0bfc743ad46f316c50b>> */ /** @@ -65,6 +65,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean = true + override fun enableCxxAnimatedDirectPathInBackend(): Boolean = false + override fun enableDestroyShadowTreeRevisionAsync(): Boolean = false override fun enableDoubleMeasurementFixAndroid(): 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 53af4b4edab0..396572bc93c3 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<> + * @generated SignedSource<> */ /** @@ -45,6 +45,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableBridgelessArchitectureCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null + private var enableCxxAnimatedDirectPathInBackendCache: Boolean? = null private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null private var enableDoubleMeasurementFixAndroidCache: Boolean? = null private var enableEagerRootViewAttachmentCache: Boolean? = null @@ -332,6 +333,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableCxxAnimatedDirectPathInBackend(): Boolean { + var cached = enableCxxAnimatedDirectPathInBackendCache + if (cached == null) { + cached = currentProvider.enableCxxAnimatedDirectPathInBackend() + accessedFeatureFlags.add("enableCxxAnimatedDirectPathInBackend") + enableCxxAnimatedDirectPathInBackendCache = cached + } + return cached + } + override fun enableDestroyShadowTreeRevisionAsync(): Boolean { var cached = enableDestroyShadowTreeRevisionAsyncCache 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 8fa2f4269464..652bfaa434ce 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<> + * @generated SignedSource<> */ /** @@ -65,6 +65,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean + @DoNotStrip public fun enableCxxAnimatedDirectPathInBackend(): Boolean + @DoNotStrip public fun enableDestroyShadowTreeRevisionAsync(): Boolean @DoNotStrip public fun enableDoubleMeasurementFixAndroid(): 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 23fd0978a766..f5e7a408c311 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<<944781e7a547e601f0637869a3a3ec5e>> + * @generated SignedSource<<30b22c622145cb7664db5550e6d82440>> */ /** @@ -165,6 +165,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableCxxAnimatedDirectPathInBackend() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableCxxAnimatedDirectPathInBackend"); + return method(javaProvider_); + } + bool enableDestroyShadowTreeRevisionAsync() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableDestroyShadowTreeRevisionAsync"); @@ -730,6 +736,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableCustomFocusSearchOnClippedElement return ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableCxxAnimatedDirectPathInBackend( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableDestroyShadowTreeRevisionAsync( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync(); @@ -1204,6 +1215,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableCustomFocusSearchOnClippedElementsAndroid", JReactNativeFeatureFlagsCxxInterop::enableCustomFocusSearchOnClippedElementsAndroid), + makeNativeMethod( + "enableCxxAnimatedDirectPathInBackend", + JReactNativeFeatureFlagsCxxInterop::enableCxxAnimatedDirectPathInBackend), makeNativeMethod( "enableDestroyShadowTreeRevisionAsync", JReactNativeFeatureFlagsCxxInterop::enableDestroyShadowTreeRevisionAsync), 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 ae774f80c82b..c3c1b05298b3 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<> + * @generated SignedSource<<80d466e62bb754e4ed9b6f46f9b9b546>> */ /** @@ -93,6 +93,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableCustomFocusSearchOnClippedElementsAndroid( facebook::jni::alias_ref); + static bool enableCxxAnimatedDirectPathInBackend( + facebook::jni::alias_ref); + static bool enableDestroyShadowTreeRevisionAsync( 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 ac8ddb96a112..f2190e4322aa 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<<56e127471f63e47798ec619e6fa56738>> + * @generated SignedSource<> */ /** @@ -110,6 +110,10 @@ bool ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid() return getAccessor().enableCustomFocusSearchOnClippedElementsAndroid(); } +bool ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend() { + return getAccessor().enableCxxAnimatedDirectPathInBackend(); +} + bool ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync() { return getAccessor().enableDestroyShadowTreeRevisionAsync(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 0e5b3f2013fb..510bc1d168ec 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<<6887b671526bc8a3b0678e3e45b4e3b4>> + * @generated SignedSource<> */ /** @@ -146,6 +146,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableCustomFocusSearchOnClippedElementsAndroid(); + /** + * With the shared animation backend, C++ Animated keeps its non-layout values out of React commits and commits the final values once per surface when animations end, as it does without the backend. + */ + RN_EXPORT static bool enableCxxAnimatedDirectPathInBackend(); + /** * Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 7640cea6418a..69f5810e7c5e 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<<5d9712bd7e62ba3a29b084ba4c60bec5>> + * @generated SignedSource<<4d3c3cb16e3dc5ab870ffc28a1a5a4c8>> */ /** @@ -407,6 +407,24 @@ bool ReactNativeFeatureFlagsAccessor::enableCustomFocusSearchOnClippedElementsAn return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableCxxAnimatedDirectPathInBackend() { + auto flagValue = enableCxxAnimatedDirectPathInBackend_.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(21, "enableCxxAnimatedDirectPathInBackend"); + + flagValue = currentProvider_->enableCxxAnimatedDirectPathInBackend(); + enableCxxAnimatedDirectPathInBackend_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() { auto flagValue = enableDestroyShadowTreeRevisionAsync_.load(); @@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(21, "enableDestroyShadowTreeRevisionAsync"); + markFlagAsAccessed(22, "enableDestroyShadowTreeRevisionAsync"); flagValue = currentProvider_->enableDestroyShadowTreeRevisionAsync(); enableDestroyShadowTreeRevisionAsync_ = flagValue; @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableDoubleMeasurementFixAndroid"); + markFlagAsAccessed(23, "enableDoubleMeasurementFixAndroid"); flagValue = currentProvider_->enableDoubleMeasurementFixAndroid(); enableDoubleMeasurementFixAndroid_ = flagValue; @@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableEagerRootViewAttachment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(23, "enableEagerRootViewAttachment"); + markFlagAsAccessed(24, "enableEagerRootViewAttachment"); flagValue = currentProvider_->enableEagerRootViewAttachment(); enableEagerRootViewAttachment_ = flagValue; @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableExclusivePropsUpdateAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableExclusivePropsUpdateAndroid"); + markFlagAsAccessed(25, "enableExclusivePropsUpdateAndroid"); flagValue = currentProvider_->enableExclusivePropsUpdateAndroid(); enableExclusivePropsUpdateAndroid_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranching() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableFabricCommitBranching"); + markFlagAsAccessed(26, "enableFabricCommitBranching"); flagValue = currentProvider_->enableFabricCommitBranching(); enableFabricCommitBranching_ = flagValue; @@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranchingMergeOnMainThre // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(26, "enableFabricCommitBranchingMergeOnMainThread"); + markFlagAsAccessed(27, "enableFabricCommitBranchingMergeOnMainThread"); flagValue = currentProvider_->enableFabricCommitBranchingMergeOnMainThread(); enableFabricCommitBranchingMergeOnMainThread_ = flagValue; @@ -524,7 +542,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(27, "enableFabricLogs"); + markFlagAsAccessed(28, "enableFabricLogs"); flagValue = currentProvider_->enableFabricLogs(); enableFabricLogs_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFlexboxAutoMinSizeInStrictMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableFlexboxAutoMinSizeInStrictMode"); + markFlagAsAccessed(29, "enableFlexboxAutoMinSizeInStrictMode"); flagValue = currentProvider_->enableFlexboxAutoMinSizeInStrictMode(); enableFlexboxAutoMinSizeInStrictMode_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFollowUpMountItemDispatchAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableFollowUpMountItemDispatchAndroid"); + markFlagAsAccessed(30, "enableFollowUpMountItemDispatchAndroid"); flagValue = currentProvider_->enableFollowUpMountItemDispatchAndroid(); enableFollowUpMountItemDispatchAndroid_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSCompressedTextFrameAdjustment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableIOSCompressedTextFrameAdjustment"); + markFlagAsAccessed(31, "enableIOSCompressedTextFrameAdjustment"); flagValue = currentProvider_->enableIOSCompressedTextFrameAdjustment(); enableIOSCompressedTextFrameAdjustment_ = flagValue; @@ -596,7 +614,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(31, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(32, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -614,7 +632,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(32, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(33, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -632,7 +650,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(33, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(34, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImageTransparentTintColor() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableImageTransparentTintColor"); + markFlagAsAccessed(35, "enableImageTransparentTintColor"); flagValue = currentProvider_->enableImageTransparentTintColor(); enableImageTransparentTintColor_ = flagValue; @@ -668,7 +686,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(35, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(36, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableImperativeEvents"); + markFlagAsAccessed(37, "enableImperativeEvents"); flagValue = currentProvider_->enableImperativeEvents(); enableImperativeEvents_ = flagValue; @@ -704,7 +722,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(37, "enableImperativeFocus"); + markFlagAsAccessed(38, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -722,7 +740,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(38, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(39, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -740,7 +758,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(39, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(40, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -758,7 +776,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(40, "enableKeyEvents"); + markFlagAsAccessed(41, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -776,7 +794,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(41, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(42, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -794,7 +812,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(42, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(43, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -812,7 +830,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(43, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(44, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMountingCoordinatorPullModelAndroid( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enableMountingCoordinatorPullModelAndroid"); + markFlagAsAccessed(45, "enableMountingCoordinatorPullModelAndroid"); flagValue = currentProvider_->enableMountingCoordinatorPullModelAndroid(); enableMountingCoordinatorPullModelAndroid_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMutationObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "enableMutationObserverByDefault"); + markFlagAsAccessed(46, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -866,7 +884,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(46, "enableNativeCSSParsing"); + markFlagAsAccessed(47, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreallocatedPropsDiffOnInsertAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "enablePreallocatedPropsDiffOnInsertAndroid"); + markFlagAsAccessed(48, "enablePreallocatedPropsDiffOnInsertAndroid"); flagValue = currentProvider_->enablePreallocatedPropsDiffOnInsertAndroid(); enablePreallocatedPropsDiffOnInsertAndroid_ = flagValue; @@ -902,7 +920,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(48, "enablePreparedTextLayout"); + markFlagAsAccessed(49, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -920,7 +938,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(49, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(50, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResizeObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "enableResizeObserverByDefault"); + markFlagAsAccessed(51, "enableResizeObserverByDefault"); flagValue = currentProvider_->enableResizeObserverByDefault(); enableResizeObserverByDefault_ = flagValue; @@ -956,7 +974,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(51, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(52, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -974,7 +992,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(52, "enableViewCulling"); + markFlagAsAccessed(53, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -992,7 +1010,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(53, "enableViewRecycling"); + markFlagAsAccessed(54, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -1010,7 +1028,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(54, "enableViewRecyclingForImage"); + markFlagAsAccessed(55, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -1028,7 +1046,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(55, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(56, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -1046,7 +1064,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(56, "enableViewRecyclingForText"); + markFlagAsAccessed(57, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -1064,7 +1082,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(57, "enableViewRecyclingForView"); + markFlagAsAccessed(58, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -1082,7 +1100,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(58, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(59, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fixBorderlessRippleAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fixBorderlessRippleAndroid"); + markFlagAsAccessed(60, "fixBorderlessRippleAndroid"); flagValue = currentProvider_->fixBorderlessRippleAndroid(); fixBorderlessRippleAndroid_ = flagValue; @@ -1118,7 +1136,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(60, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(61, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::fixTextLayoutOnPixelDensityChange() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "fixTextLayoutOnPixelDensityChange"); + markFlagAsAccessed(62, "fixTextLayoutOnPixelDensityChange"); flagValue = currentProvider_->fixTextLayoutOnPixelDensityChange(); fixTextLayoutOnPixelDensityChange_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::fixYogaFlexBasisFitContentInMainAxis() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(63, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1172,7 +1190,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(63, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(64, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1190,7 +1208,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(64, "fuseboxEnabledRelease"); + markFlagAsAccessed(65, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxFrameRecordingEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(66, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxScreenshotCaptureEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(67, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxWebSocketEventsEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "fuseboxWebSocketEventsEnabled"); + markFlagAsAccessed(68, "fuseboxWebSocketEventsEnabled"); flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); fuseboxWebSocketEventsEnabled_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::keepTimersRunningWhileHostPausedAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "keepTimersRunningWhileHostPausedAndroid"); + markFlagAsAccessed(69, "keepTimersRunningWhileHostPausedAndroid"); flagValue = currentProvider_->keepTimersRunningWhileHostPausedAndroid(); keepTimersRunningWhileHostPausedAndroid_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(70, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1298,7 +1316,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(70, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(71, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1316,7 +1334,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(71, "perfIssuesEnabled"); + markFlagAsAccessed(72, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1334,7 +1352,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(72, "perfMonitorV2Enabled"); + markFlagAsAccessed(73, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1352,7 +1370,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(73, "preparedTextCacheSize"); + markFlagAsAccessed(74, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1370,7 +1388,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(74, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(75, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2Android() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "redBoxV2Android"); + markFlagAsAccessed(76, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1406,7 +1424,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2IOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "redBoxV2IOS"); + markFlagAsAccessed(77, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1424,7 +1442,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(77, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(78, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1442,7 +1460,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(78, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(79, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1460,7 +1478,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(79, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(80, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::skipUnchangedChildrenInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "skipUnchangedChildrenInDifferentiator"); + markFlagAsAccessed(81, "skipUnchangedChildrenInDifferentiator"); flagValue = currentProvider_->skipUnchangedChildrenInDifferentiator(); skipUnchangedChildrenInDifferentiator_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipBoundsWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(82, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1514,7 +1532,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(82, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(83, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1532,7 +1550,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(83, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(84, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommitT // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(85, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1568,7 +1586,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(85, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(86, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1586,7 +1604,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(86, "useFabricInterop"); + markFlagAsAccessed(87, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1604,7 +1622,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(87, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(88, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1622,7 +1640,7 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(88, "useNestedScrollViewAndroid"); + markFlagAsAccessed(89, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1640,7 +1658,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(89, "useSharedAnimatedBackend"); + markFlagAsAccessed(90, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1658,7 +1676,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(90, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(91, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1676,7 +1694,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(91, "useTurboModuleInterop"); + markFlagAsAccessed(92, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1694,7 +1712,7 @@ bool ReactNativeFeatureFlagsAccessor::useWindowDensityForDisplayMetricsAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(92, "useWindowDensityForDisplayMetricsAndroid"); + markFlagAsAccessed(93, "useWindowDensityForDisplayMetricsAndroid"); flagValue = currentProvider_->useWindowDensityForDisplayMetricsAndroid(); useWindowDensityForDisplayMetricsAndroid_ = flagValue; @@ -1712,7 +1730,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(93, "viewCullingOutsetRatio"); + markFlagAsAccessed(94, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1730,7 +1748,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(94, "viewTransitionEnabled"); + markFlagAsAccessed(95, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1748,7 +1766,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionUseHardwareBitmapAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(95, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(96, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1766,7 +1784,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(96, "virtualViewPrerenderRatio"); + markFlagAsAccessed(97, "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 7a9bc59bc57e..526c2195d1f1 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<<17f41335dce7e5b45e7af2878c47d69b>> + * @generated SignedSource<<9b94906738b9b49a170518b1a9080d1a>> */ /** @@ -55,6 +55,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableBridgelessArchitecture(); bool enableCppPropsIteratorSetter(); bool enableCustomFocusSearchOnClippedElementsAndroid(); + bool enableCxxAnimatedDirectPathInBackend(); bool enableDestroyShadowTreeRevisionAsync(); bool enableDoubleMeasurementFixAndroid(); bool enableEagerRootViewAttachment(); @@ -142,7 +143,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 97> accessedFeatureFlags_; + std::array, 98> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -165,6 +166,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableBridgelessArchitecture_; std::atomic> enableCppPropsIteratorSetter_; std::atomic> enableCustomFocusSearchOnClippedElementsAndroid_; + std::atomic> enableCxxAnimatedDirectPathInBackend_; std::atomic> enableDestroyShadowTreeRevisionAsync_; std::atomic> enableDoubleMeasurementFixAndroid_; std::atomic> enableEagerRootViewAttachment_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 600aab1882cc..1e454166e33a 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<<35e8175277baa31d135d802ef5a5e6f1>> + * @generated SignedSource<<58a960bb7b1b1c3440c09b63cc90ff32>> */ /** @@ -113,6 +113,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return true; } + bool enableCxxAnimatedDirectPathInBackend() override { + return false; + } + bool enableDestroyShadowTreeRevisionAsync() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 88878807cfec..e923c67aea9b 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<> + * @generated SignedSource<> */ /** @@ -236,6 +236,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableCustomFocusSearchOnClippedElementsAndroid(); } + bool enableCxxAnimatedDirectPathInBackend() override { + auto value = values_["enableCxxAnimatedDirectPathInBackend"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableCxxAnimatedDirectPathInBackend(); + } + bool enableDestroyShadowTreeRevisionAsync() override { auto value = values_["enableDestroyShadowTreeRevisionAsync"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 664922287009..941d95fe044b 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<<7413e958441d0502dc1266ff87e9da85>> + * @generated SignedSource<<27ca5cfa81c357649ce47dac6c59ae65>> */ /** @@ -48,6 +48,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableBridgelessArchitecture() = 0; virtual bool enableCppPropsIteratorSetter() = 0; virtual bool enableCustomFocusSearchOnClippedElementsAndroid() = 0; + virtual bool enableCxxAnimatedDirectPathInBackend() = 0; virtual bool enableDestroyShadowTreeRevisionAsync() = 0; virtual bool enableDoubleMeasurementFixAndroid() = 0; virtual bool enableEagerRootViewAttachment() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index ca8a80f1adbf..b844a3fefadf 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<> + * @generated SignedSource<> */ /** @@ -149,6 +149,11 @@ bool NativeReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndr return ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid(); } +bool NativeReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend(); +} + bool NativeReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 7413766e285a..0fd3034f2da4 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<<90f271ea47f91e6a84979209b7b3d816>> + * @generated SignedSource<> */ /** @@ -80,6 +80,8 @@ class NativeReactNativeFeatureFlags bool enableCustomFocusSearchOnClippedElementsAndroid(jsi::Runtime& runtime); + bool enableCxxAnimatedDirectPathInBackend(jsi::Runtime& runtime); + bool enableDestroyShadowTreeRevisionAsync(jsi::Runtime& runtime); bool enableDoubleMeasurementFixAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index de87a75b0c5a..83a47b158e78 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -555,11 +555,17 @@ void NativeAnimatedNodesManager::handleAnimatedEvent( // frames. if (useSharedAnimatedBackend_) { if (auto animationBackend = animationBackend_.lock()) { + AnimationFrontendId frontendId = 0; + { + std::lock_guard lock(animationBackendCallbackMutex_); + frontendId = animationFrontendIdLocked(*animationBackend); + } // Pass stepAnimations false so this push only propagates event values. // Frames step time-based drivers with the vsync time, which can be // earlier than this push's time, so stepping them here would move them // backward on the next frame. animationBackend->pushAnimationMutations( + frontendId, [this](AnimationTimestamp timestamp) -> AnimationMutations { return pullAnimationMutations( timestamp, /* stepAnimations */ false); @@ -609,23 +615,9 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) { if (!animationBackend) { return; } - // AnimationBackend::start registers the callback before returning the id. - // Keep registration and id publication in one critical section; otherwise a - // concurrent stop can observe no id and leave the registered callback - // orphaned in the backend. std::lock_guard lock(animationBackendCallbackMutex_); - if (animationBackendCallbackId_.has_value()) { - return; - } - auto weak = weak_from_this(); - auto callbackId = animationBackend->start( - [weak](AnimationTimestamp timestamp) -> AnimationMutations { - if (auto self = weak.lock()) { - return self->pullAnimationMutations(timestamp); - } - return {}; - }); - animationBackendCallbackId_ = callbackId; + animationBackend->startFrontend( + animationFrontendIdLocked(*animationBackend)); } } @@ -651,24 +643,31 @@ void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded( { auto animationBackend = animationBackend_.lock(); - CallbackId callbackId = 0; - { - std::lock_guard lock(animationBackendCallbackMutex_); - if (!animationBackendCallbackId_.has_value()) { - return; - } - // Clear the published id while holding the same mutex that protects - // start registration. This makes a concurrent start either see an active - // callback or wait until this stop owns the callback id. - callbackId = *animationBackendCallbackId_; - animationBackendCallbackId_.reset(); - } - if (animationBackend) { - animationBackend->stop(callbackId); + std::lock_guard lock(animationBackendCallbackMutex_); + if (animationBackend && animationFrontendId_.has_value()) { + animationBackend->stopFrontend(*animationFrontendId_); } } } +AnimationFrontendId NativeAnimatedNodesManager::animationFrontendIdLocked( + UIManagerAnimationBackend& animationBackend) { + if (!animationFrontendId_.has_value()) { + animationFrontendId_ = animationBackend.registerFrontend( + {.commitDirectProps = + !ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend(), + .onAnimationFrame = + [weak = weak_from_this()]( + AnimationTimestamp timestamp) -> AnimationMutations { + if (auto self = weak.lock()) { + return self->pullAnimationMutations(timestamp); + } + return {}; + }}); + } + return *animationFrontendId_; +} + bool NativeAnimatedNodesManager::isAnimationUpdateNeeded() const noexcept { return !activeAnimations_.empty() || !updatedNodeTags_.empty() || isEventAnimationInProgress_; @@ -1027,7 +1026,8 @@ void NativeAnimatedNodesManager::insertMutations( if (auto family = weakFamily.lock()) { propsBuilder.storeDynamic(update.second); - if (shouldRequestAsyncFlush_.contains(tag)) { + bool finished = shouldRequestAsyncFlush_.contains(tag); + if (finished) { mutations.asyncFlushSurfaces.insert(family->getSurfaceId()); } mutations.batch.push_back( @@ -1036,6 +1036,7 @@ void NativeAnimatedNodesManager::insertMutations( .family = family, .props = propsBuilder.get(), .hasLayoutUpdates = hasLayoutUpdates, + .finishesAnimation = finished, }); } } diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h index a85cbf4e6d95..6154d6468a2e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h @@ -219,6 +219,8 @@ class NativeAnimatedNodesManager : public std::enable_shared_from_this animationBackendCallbackId_; + // Registered on the first start or event, and kept until the backend is + // destroyed. + std::optional animationFrontendId_; friend class ColorAnimatedNode; friend class AnimationDriver; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index 7fe342f04d52..bd9a12b0d26c 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -8,6 +8,7 @@ #include "NativeAnimatedNodesManagerProvider.h" #include +#include #include #include #include @@ -104,29 +105,7 @@ NativeAnimatedNodesManagerProvider::getOrCreate( std::make_shared( nativeAnimatedNodesManager_); - animatedMountingOverrideDelegate_ = - std::make_shared( - *nativeAnimatedNodesManager_, *scheduler); - - // Register on existing surfaces - uiManager->getShadowTreeRegistry().enumerate( - [animatedMountingOverrideDelegate = - std::weak_ptr( - animatedMountingOverrideDelegate_)]( - const ShadowTree& shadowTree, bool& /*stop*/) { - shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( - animatedMountingOverrideDelegate); - }); - - // Register on surfaces started in the future - uiManager->addOnSurfaceStartCallback( - [animatedMountingOverrideDelegate = - std::weak_ptr( - animatedMountingOverrideDelegate_)]( - const ShadowTree& shadowTree) { - shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( - animatedMountingOverrideDelegate); - }); + addMountingOverrideDelegate(*uiManager); uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_); } else { @@ -138,6 +117,10 @@ NativeAnimatedNodesManagerProvider::getOrCreate( nativeAnimatedNodesManager_ = std::make_shared(animationBackend); + + if (ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend()) { + addMountingOverrideDelegate(*uiManager); + } } addEventEmitterListener( @@ -164,6 +147,35 @@ NativeAnimatedNodesManagerProvider::getOrCreate( return nativeAnimatedNodesManager_; } +void NativeAnimatedNodesManagerProvider::addMountingOverrideDelegate( + UIManager& uiManager) { + // TODO: remove force casting. + auto* scheduler = (Scheduler*)uiManager.getDelegate(); + animatedMountingOverrideDelegate_ = + std::make_shared( + *nativeAnimatedNodesManager_, *scheduler); + + // Register on existing surfaces + uiManager.getShadowTreeRegistry().enumerate( + [animatedMountingOverrideDelegate = + std::weak_ptr( + animatedMountingOverrideDelegate_)]( + const ShadowTree& shadowTree, bool& /*stop*/) { + shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( + animatedMountingOverrideDelegate); + }); + + // Register on surfaces started in the future + uiManager.addOnSurfaceStartCallback( + [animatedMountingOverrideDelegate = + std::weak_ptr( + animatedMountingOverrideDelegate_)]( + const ShadowTree& shadowTree) { + shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( + animatedMountingOverrideDelegate); + }); +} + void NativeAnimatedNodesManagerProvider::addEventEmitterListener( const std::shared_ptr& listener) { eventEmitterListenerContainer_->addListener(listener); diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h index f62c6136de34..d801c8013d89 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h @@ -16,6 +16,7 @@ namespace facebook::react { class AnimatedMountingOverrideDelegate; +class UIManager; class NativeAnimatedNodesManagerProvider { public: @@ -34,6 +35,8 @@ class NativeAnimatedNodesManagerProvider { std::shared_ptr getEventEmitterListener(); private: + void addMountingOverrideDelegate(UIManager &uiManager); + std::shared_ptr nativeAnimatedNodesManager_; std::shared_ptr eventEmitterListenerContainer_; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimationTestsBase.h b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimationTestsBase.h index 47bcc9994956..501dec25ef17 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimationTestsBase.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimationTestsBase.h @@ -40,6 +40,11 @@ class AnimationTestsBase : public testing::Test { NativeAnimatedNodesManager::isOnRenderThread_ = true; } + static void runOnRenderThread() noexcept + { + NativeAnimatedNodesManager::isOnRenderThread_ = true; + } + bool nodeNeedsUpdate(Tag nodeTag) const { return nodesManager_->updatedNodeTags_.contains(nodeTag); diff --git a/packages/react-native/ReactCommon/react/renderer/animated/tests/EventAnimationDriverTests.cpp b/packages/react-native/ReactCommon/react/renderer/animated/tests/EventAnimationDriverTests.cpp index d7ffcb9845df..943a6bd7b26e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/tests/EventAnimationDriverTests.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/tests/EventAnimationDriverTests.cpp @@ -22,6 +22,12 @@ class FakeAnimationBackend : public UIManagerAnimationBackend { AnimationTimestamp pushTimestamp{0}; void onAnimationFrame(AnimationTimestamp /*timestamp*/) override {} + AnimationFrontendId registerFrontend( + AnimationFrontend /*frontend*/) override { + return 1; + } + void startFrontend(AnimationFrontendId /*frontendId*/) override {} + void stopFrontend(AnimationFrontendId /*frontendId*/) override {} CallbackId start(const Callback& /*callback*/) override { return 0; } @@ -32,6 +38,11 @@ class FakeAnimationBackend : public UIManagerAnimationBackend { void pushAnimationMutations(const Callback& callback) override { callback(pushTimestamp); } + void pushAnimationMutations( + AnimationFrontendId /*frontendId*/, + const Callback& callback) override { + callback(pushTimestamp); + } void registerJSInvoker(std::shared_ptr /*jsInvoker*/) override {} }; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/tests/NativeAnimatedFrontendTests.cpp b/packages/react-native/ReactCommon/react/renderer/animated/tests/NativeAnimatedFrontendTests.cpp new file mode 100644 index 000000000000..e4cb9b9a8656 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animated/tests/NativeAnimatedFrontendTests.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "AnimationTestsBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace { + +class RecordingAnimationBackend : public UIManagerAnimationBackend { + public: + std::vector frontends; + std::vector startedFrontends; + int startedCallbacks{0}; + + void onAnimationFrame(AnimationTimestamp /*timestamp*/) override {} + AnimationFrontendId registerFrontend(AnimationFrontend frontend) override { + frontends.push_back(std::move(frontend)); + return static_cast(frontends.size()); + } + void startFrontend(AnimationFrontendId frontendId) override { + startedFrontends.push_back(frontendId); + } + void stopFrontend(AnimationFrontendId /*frontendId*/) override {} + void pushAnimationMutations( + AnimationFrontendId /*frontendId*/, + const Callback& /*callback*/) override {} + CallbackId start(const Callback& /*callback*/) override { + startedCallbacks++; + return 0; + } + void stop(CallbackId /*callbackId*/) override {} + void clearRegistry(SurfaceId /*surfaceId*/) override {} + void clearRegistryOnSurfaceStop(SurfaceId /*surfaceId*/) override {} + void trigger() override {} + void pushAnimationMutations(const Callback& /*callback*/) override {} + void registerJSInvoker(std::shared_ptr /*jsInvoker*/) override {} +}; + +class DirectPathFlags : public ReactNativeFeatureFlagsDefaults { + public: + explicit DirectPathFlags(bool directPath) : directPath_(directPath) {} + + bool enableCxxAnimatedDirectPathInBackend() override { + return directPath_; + } + + private: + bool directPath_; +}; + +constexpr Tag kValueNode = 1; +constexpr Tag kStyleNode = 2; +constexpr Tag kPropsNode = 3; +constexpr Tag kView = 10; + +} // namespace + +class NativeAnimatedFrontendTests : public AnimationTestsBase { + protected: + void SetUp() override { + runOnRenderThread(); + } + + void TearDown() override { + ReactNativeFeatureFlags::dangerouslyReset(); + } + + // Starts animating the view's opacity in four frames. + void startAnimation() { + manager_ = std::make_shared(backend_); + manager_->createAnimatedNode( + kValueNode, + folly::dynamic::object("type", "value")("value", 0)("offset", 0)); + manager_->createAnimatedNode( + kStyleNode, + folly::dynamic::object("type", "style")( + "style", folly::dynamic::object("opacity", kValueNode))); + manager_->createAnimatedNode( + kPropsNode, + folly::dynamic::object("type", "props")( + "props", folly::dynamic::object("style", kStyleNode))); + manager_->connectAnimatedNodes(kValueNode, kStyleNode); + manager_->connectAnimatedNodes(kStyleNode, kPropsNode); + manager_->connectAnimatedNodeToView(kPropsNode, kView); + manager_->connectAnimatedNodeToShadowNodeFamily( + kPropsNode, + std::const_pointer_cast(view_->getFamilyShared())); + manager_->startAnimatingNode( + 1, + kValueNode, + folly::dynamic::object("type", "frames")( + "frames", folly::dynamic::array(0.0, 0.5, 1.0))("toValue", 1), + std::nullopt); + manager_->startRenderCallbackIfNeeded(false); + } + + // Animates the view's opacity and returns whether each frame's mutation of + // the view finishes the animation. + std::vector finishesAnimationPerFrame(bool directPath) { + ReactNativeFeatureFlags::dangerouslyForceOverride( + std::make_unique(directPath)); + startAnimation(); + + std::vector finishes; + for (int frame = 0; frame < 4; frame++) { + auto mutations = manager_->pullAnimationMutations( + AnimationTimestamp(1000 + SingleFrameIntervalMs * frame)); + for (const auto& mutation : mutations.batch) { + if (mutation.tag == kView) { + finishes.push_back(mutation.finishesAnimation); + } + } + } + return finishes; + } + + std::shared_ptr backend_ = + std::make_shared(); + std::shared_ptr manager_; + ComponentBuilder builder_ = + simpleComponentBuilder(std::make_shared()); + std::shared_ptr view_ = + builder_.build(Element().surfaceId(1).tag(kView)); +}; + +TEST_F(NativeAnimatedFrontendTests, registersAndStartsAFrontend) { + startAnimation(); + + ASSERT_EQ(backend_->frontends.size(), 1u); + EXPECT_EQ(backend_->startedFrontends, std::vector{1}); + EXPECT_EQ(backend_->startedCallbacks, 0); +} + +TEST_F(NativeAnimatedFrontendTests, onlyTheLastFrameFinishesTheAnimation) { + auto finishes = finishesAnimationPerFrame(true); + ASSERT_GE(finishes.size(), 2u); + for (size_t frame = 0; frame + 1 < finishes.size(); frame++) { + EXPECT_FALSE(finishes[frame]) << "frame " << frame; + } + EXPECT_TRUE(finishes.back()); +} + +TEST_F( + NativeAnimatedFrontendTests, + registersAFrontendThatKeepsDirectValuesOutOfReactCommits) { + finishesAnimationPerFrame(true); + + ASSERT_EQ(backend_->frontends.size(), 1u); + EXPECT_FALSE(backend_->frontends[0].commitDirectProps); + EXPECT_EQ(backend_->startedFrontends, std::vector{1}); + EXPECT_EQ(backend_->startedCallbacks, 0); +} + +TEST_F( + NativeAnimatedFrontendTests, + registersAFrontendThatCommitsDirectValuesWithoutTheFlag) { + finishesAnimationPerFrame(false); + + ASSERT_EQ(backend_->frontends.size(), 1u); + EXPECT_TRUE(backend_->frontends[0].commitDirectProps); + EXPECT_EQ(backend_->startedFrontends, std::vector{1}); + EXPECT_EQ(backend_->startedCallbacks, 0); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp index bca9b7a82d01..2357225d4fb1 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp @@ -8,7 +8,9 @@ #include "AnimatedPropsRegistry.h" #include #include +#include #include "AnimatedProps.h" +#include "AnimatedPropsSerializer.h" namespace facebook::react { @@ -25,9 +27,9 @@ void mergeAnimatedRawProps( } void AnimatedPropsRegistry::update( - const std::vector& batches) { + const std::vector& batches) { auto lock = std::lock_guard(mutex_); - for (const auto& mutations : batches) { + for (const auto& [commitDirectProps, mutations] : batches) { for (const auto& mutation : mutations.batch) { const auto& family = mutation.family; react_native_assert(family != nullptr); @@ -36,6 +38,23 @@ void AnimatedPropsRegistry::update( continue; } auto& surfaceContext = contextIt->second; + // Without `commitDirectProps`, only layout values are written into React + // commits, and the last values of animations by the end-of-animation + // commit. + if (!commitDirectProps && !mutation.hasLayoutUpdates) { + forgetRawProps(surfaceContext, mutation); + if (mutation.finishesAnimation) { + auto& finalValues = surfaceContext.finalValues[mutation.tag]; + if (finalValues.props.isNull()) { + finalValues.props = folly::dynamic::object(); + } + finalValues.family = family; + mergeAnimatedRawProps( + finalValues.props, + animationbackend::packAnimatedProps(mutation.props)); + } + continue; + } auto& pendingMap = surfaceContext.pendingMap; surfaceContext.pendingFamilies.insert(family); const auto tag = mutation.tag; @@ -72,6 +91,41 @@ void AnimatedPropsRegistry::update( } } +void AnimatedPropsRegistry::forgetRawProps( + SurfaceContext& surfaceContext, + const AnimationMutation& mutation) { + const auto& rawProps = mutation.props.rawProps; + if (!rawProps) { + return; + } + auto pendingIt = surfaceContext.pendingMap.find(mutation.tag); + auto mapIt = surfaceContext.map.find(mutation.tag); + if (pendingIt == surfaceContext.pendingMap.end() && + mapIt == surfaceContext.map.end()) { + return; + } + folly::dynamic converted; + const auto* values = rawProps->getDynamic(); + if (values == nullptr) { + converted = rawProps->toDynamic(); + values = &converted; + } + if (pendingIt != surfaceContext.pendingMap.end() && + pendingIt->second->rawProps) { + for (const auto& [key, _] : values->items()) { + pendingIt->second->rawProps->erase(key); + } + } + if (mapIt != surfaceContext.map.end() && mapIt->second->rawProps) { + auto& keys = surfaceContext.staleRawKeys[mutation.tag]; + for (const auto& [key, _] : values->items()) { + if (mapIt->second->rawProps->count(key) != 0) { + keys.insert(key.asString()); + } + } + } +} + void AnimatedPropsRegistry::initializeSurface(SurfaceId surfaceId) { auto lock = std::lock_guard(mutex_); surfaceContexts_.try_emplace(surfaceId); @@ -82,9 +136,18 @@ std::pair< SnapshotMap&> AnimatedPropsRegistry::getMap(SurfaceId surfaceId) { auto lock = std::lock_guard(mutex_); - auto& [pendingMap, map, pendingFamilies, families] = + auto& [pendingMap, map, pendingFamilies, families, finalValues, staleRawKeys] = surfaceContexts_[surfaceId]; + for (const auto& [tag, keys] : staleRawKeys) { + if (auto it = map.find(tag); it != map.end() && it->second->rawProps) { + for (const auto& key : keys) { + it->second->rawProps->erase(key); + } + } + } + staleRawKeys.clear(); + for (auto& family : pendingFamilies) { families.insert(family); } @@ -119,6 +182,43 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) { return {families, map}; } +std::unordered_map AnimatedPropsRegistry::takeFinalValues( + SurfaceId surfaceId) { + auto lock = std::lock_guard(mutex_); + if (auto it = surfaceContexts_.find(surfaceId); + it != surfaceContexts_.end()) { + return std::exchange(it->second.finalValues, {}); + } + return {}; +} + +void AnimatedPropsRegistry::restoreFinalValues( + SurfaceId surfaceId, + std::unordered_map finalValues) { + auto lock = std::lock_guard(mutex_); + auto it = surfaceContexts_.find(surfaceId); + if (it == surfaceContexts_.end()) { + return; + } + for (auto& [tag, newer] : it->second.finalValues) { + auto [restored, inserted] = finalValues.try_emplace(tag, std::move(newer)); + if (!inserted) { + mergeAnimatedRawProps(restored->second.props, newer.props); + } + } + it->second.finalValues = std::move(finalValues); +} + +bool AnimatedPropsRegistry::scheduleAsyncFlush(SurfaceId surfaceId) { + std::lock_guard lock(mutex_); + return scheduledAsyncFlushSurfaces_.insert(surfaceId).second; +} + +void AnimatedPropsRegistry::startAsyncFlush(SurfaceId surfaceId) { + std::lock_guard lock(mutex_); + scheduledAsyncFlushSurfaces_.erase(surfaceId); +} + void AnimatedPropsRegistry::clear(SurfaceId surfaceId) { auto lock = std::lock_guard(mutex_); if (auto it = surfaceContexts_.find(surfaceId); @@ -126,6 +226,7 @@ void AnimatedPropsRegistry::clear(SurfaceId surfaceId) { auto& surfaceContext = it->second; surfaceContext.families.clear(); surfaceContext.map.clear(); + surfaceContext.staleRawKeys.clear(); } } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h index 87444a9a5aa0..27f97b65d627 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h @@ -26,9 +26,27 @@ struct PropsSnapshot { std::unique_ptr rawProps; }; +// The last values of an animation whose direct values are not written into +// React commits. Only the end-of-animation commit writes them. +struct FinalValues { + std::shared_ptr family; + folly::dynamic props; +}; + +// The mutations of one frame or event, with the `commitDirectProps` of the +// frontend that produced them. +struct FrontendMutations { + bool commitDirectProps{true}; + AnimationMutations mutations; +}; + struct SurfaceContext { std::unordered_map> pendingMap, map; std::unordered_set> pendingFamilies, families; + std::unordered_map finalValues; + // Raw props written later without syncing with the commit hook, to be erased + // from `map` when it is next read under the lock. + std::unordered_map> staleRawKeys; }; using SnapshotMap = std::unordered_map>; @@ -41,14 +59,25 @@ void mergeAnimatedRawProps(folly::dynamic &target, const folly::dynamic &source) class AnimatedPropsRegistry { public: - void update(const std::vector &batches); + void update(const std::vector &batches); void initializeSurface(SurfaceId surfaceId); void clear(SurfaceId surfaceId); void clearOnSurfaceStop(SurfaceId surfaceId); std::pair> &, SnapshotMap &> getMap(SurfaceId surfaceId); + std::unordered_map takeFinalValues(SurfaceId surfaceId); + // Puts back final values that a commit did not write, under the ones recorded + // since they were taken. + void restoreFinalValues(SurfaceId surfaceId, std::unordered_map finalValues); + // Returns false when an async flush of the surface is already scheduled. + bool scheduleAsyncFlush(SurfaceId surfaceId); + void startAsyncFlush(SurfaceId surfaceId); private: + // Erases the mutation's raw props from the recorded ones, which are older. + void forgetRawProps(SurfaceContext &surfaceContext, const AnimationMutation &mutation); + std::unordered_map surfaceContexts_; + std::unordered_set scheduledAsyncFlushSurfaces_; std::mutex mutex_; }; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index 0e3db03a1024..31828c6d3adc 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -70,6 +70,58 @@ static void mergeMutation( existing.hasLayoutUpdates |= incoming.hasLayoutUpdates; } +// With enableCxxAnimatedDirectPathInBackend, the end-of-animation commit +// writes the final values recorded for its surface. +static std::unordered_map takeFinalValues( + const std::weak_ptr& weakAnimatedPropsRegistry, + SurfaceId surfaceId) { + if (!ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend()) { + return {}; + } + if (auto animatedPropsRegistry = weakAnimatedPropsRegistry.lock()) { + return animatedPropsRegistry->takeFinalValues(surfaceId); + } + return {}; +} + +static void writeFinalValues( + const std::unordered_map& finalValues, + const RootShadowNode& oldRootShadowNode, + RootShadowNode::Unshared& newRootShadowNode) { + if (finalValues.empty()) { + return; + } + std::unordered_set> families; + for (const auto& [_, values] : finalValues) { + families.insert(values.family); + } + auto cloned = + std::static_pointer_cast(oldRootShadowNode.cloneMultiple( + families, + [&](const ShadowNode& shadowNode, + const ShadowNodeFragment& fragment) { + auto props = ShadowNodeFragment::propsPlaceholder(); + if (auto it = finalValues.find(shadowNode.getTag()); + it != finalValues.end() && + families.contains(shadowNode.getFamilyShared())) { + PropsParserContext propsParserContext{ + shadowNode.getSurfaceId(), *shadowNode.getContextContainer()}; + props = shadowNode.getComponentDescriptor().cloneProps( + propsParserContext, + shadowNode.getProps(), + RawProps(it->second.props)); + } + return shadowNode.clone( + {.props = props, + .children = fragment.children, + .state = shadowNode.getState(), + .runtimeShadowNodeReference = true}); + })); + if (cloned != nullptr) { + newRootShadowNode = std::move(cloned); + } +} + AnimationBackend::AnimationBackend( std::shared_ptr animationChoreographer, std::shared_ptr uiManager) @@ -151,46 +203,68 @@ void AnimationBackend::applySurfaceUpdates( requestAsyncFlushForSurfaces(asyncFlushSurfaces); } -void AnimationBackend::applyMutations(std::vector batches) { +void AnimationBackend::applyMutations(std::vector batches) { animatedPropsRegistry_->update(batches); std::unordered_map surfaceUpdates; std::set asyncFlushSurfaces; - for (auto& mutations : batches) { - unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + for (auto& batch : batches) { + unpackMutations(batch.mutations, surfaceUpdates, asyncFlushSurfaces); } applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces); } void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) { std::vector callbacksCopy; + std::vector startedFrontends; { std::lock_guard lock(mutex_); callbacksCopy = callbacks; + for (const auto& registered : frontends_) { + if (registered.started) { + startedFrontends.push_back(registered.frontend); + } + } } // Sized up front rather than grown: MSVC's std::set move isn't noexcept, so // growing a vector of AnimationMutations would try to copy move-only props. - std::vector batches(callbacksCopy.size()); - std::transform( - callbacksCopy.begin(), - callbacksCopy.end(), - batches.begin(), - [timestamp](const CallbackWithId& callbackWithId) { - return callbackWithId.callback(timestamp); - }); + std::vector batches( + callbacksCopy.size() + startedFrontends.size()); + for (size_t i = 0; i < callbacksCopy.size(); i++) { + batches[i].mutations = callbacksCopy[i].callback(timestamp); + } + for (size_t i = 0; i < startedFrontends.size(); i++) { + auto& batch = batches[callbacksCopy.size() + i]; + batch.commitDirectProps = startedFrontends[i].commitDirectProps; + batch.mutations = startedFrontends[i].onAnimationFrame(timestamp); + } applyMutations(std::move(batches)); } +void AnimationBackend::resumeFramesLocked() { + if (!isRenderCallbackStarted_) { + animationChoreographer_->resume(); + isRenderCallbackStarted_ = true; + } +} + +void AnimationBackend::pauseFramesIfIdleLocked() { + if (isRenderCallbackStarted_ && callbacks.empty() && + std::none_of(frontends_.begin(), frontends_.end(), [](auto& frontend) { + return frontend.started; + })) { + animationChoreographer_->pause(); + isRenderCallbackStarted_ = false; + } +} + CallbackId AnimationBackend::start(const Callback& callback) { std::lock_guard lock(mutex_); auto callbackId = nextCallbackId_++; callbacks.push_back({.callbackId = callbackId, .callback = callback}); - if (!isRenderCallbackStarted_) { - animationChoreographer_->resume(); - isRenderCallbackStarted_ = true; - } + resumeFramesLocked(); return callbackId; } @@ -206,10 +280,52 @@ void AnimationBackend::stop(CallbackId callbackId) { } callbacks.erase(it); - if (isRenderCallbackStarted_ && callbacks.empty()) { - animationChoreographer_->pause(); - isRenderCallbackStarted_ = false; + pauseFramesIfIdleLocked(); +} + +AnimationFrontendId AnimationBackend::registerFrontend( + AnimationFrontend frontend) { + react_native_assert( + (frontend.commitDirectProps || + ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend()) && + "Only the direct path writes the final values of a frontend that does not commit its direct values"); + std::lock_guard lock(mutex_); + frontends_.push_back({.frontend = std::move(frontend)}); + return static_cast(frontends_.size()); +} + +void AnimationBackend::startFrontend(AnimationFrontendId frontendId) { + std::lock_guard lock(mutex_); + auto& registered = frontends_.at(frontendId - 1); + if (registered.started || !registered.frontend.onAnimationFrame) { + return; + } + registered.started = true; + resumeFramesLocked(); +} + +void AnimationBackend::stopFrontend(AnimationFrontendId frontendId) { + std::lock_guard lock(mutex_); + auto& registered = frontends_.at(frontendId - 1); + if (!registered.started) { + return; + } + registered.started = false; + pauseFramesIfIdleLocked(); +} + +void AnimationBackend::pushAnimationMutations( + AnimationFrontendId frontendId, + const Callback& callback) { + std::vector batches(1); + { + std::lock_guard lock(mutex_); + batches[0].commitDirectProps = + frontends_.at(frontendId - 1).frontend.commitDirectProps; } + auto timestamp = animationChoreographer_->now(); + batches[0].mutations = callback(timestamp); + applyMutations(std::move(batches)); } void AnimationBackend::trigger() { @@ -218,8 +334,8 @@ void AnimationBackend::trigger() { void AnimationBackend::pushAnimationMutations(const Callback& callback) { auto timestamp = animationChoreographer_->now(); - std::vector batches(1); - batches[0] = callback(timestamp); + std::vector batches(1); + batches[0].mutations = callback(timestamp); applyMutations(std::move(batches)); } @@ -290,43 +406,67 @@ void AnimationBackend::requestAsyncFlushForSurfaces( surfaces.empty() && "jsInvoker_ was not provided"); std::weak_ptr weakAnimatedPropsRegistry = animatedPropsRegistry_; + // Like C++ Animated without the backend, one flush per surface is scheduled + // at a time. It writes the final values recorded by the time it runs. + bool flushOncePerSurface = + ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend(); for (const auto& surfaceId : surfaces) { + if (flushOncePerSurface && + !animatedPropsRegistry_->scheduleAsyncFlush(surfaceId)) { + continue; + } // perform an empty commit on the js thread, to force the commit hook to // push updated shadow nodes to react through RSNRU - jsInvoker_->invokeAsync( - [weakUIManager = uiManager_, surfaceId, weakAnimatedPropsRegistry]() { - auto uiManager = weakUIManager.lock(); - if (!uiManager) { - return; - } - uiManager->getShadowTreeRegistry().visit( - surfaceId, - [weakAnimatedPropsRegistry](const ShadowTree& shadowTree) { - auto result = shadowTree.commit( - [weakAnimatedPropsRegistry]( - const RootShadowNode& oldRootShadowNode) { - return std::static_pointer_cast( + jsInvoker_->invokeAsync([weakUIManager = uiManager_, + surfaceId, + weakAnimatedPropsRegistry]() { + if (auto animatedPropsRegistry = weakAnimatedPropsRegistry.lock(); + animatedPropsRegistry && + ReactNativeFeatureFlags::enableCxxAnimatedDirectPathInBackend()) { + animatedPropsRegistry->startAsyncFlush(surfaceId); + } + auto uiManager = weakUIManager.lock(); + if (!uiManager) { + return; + } + uiManager->getShadowTreeRegistry().visit( + surfaceId, [weakAnimatedPropsRegistry](const ShadowTree& shadowTree) { + auto finalValues = takeFinalValues( + weakAnimatedPropsRegistry, shadowTree.getSurfaceId()); + auto result = shadowTree.commit( + [&finalValues](const RootShadowNode& oldRootShadowNode) { + auto newRootShadowNode = + std::static_pointer_cast( oldRootShadowNode.ShadowNode::clone({})); - }, - {.source = ShadowTreeCommitSource::AnimationEndSync}); - // To clear the registry, the updates neeed to be propagated to - // React with RSNRU. Without - // updateRuntimeShadowNodeReferencesOnCommitThread this won't - // happen if we do any commits on the main thread, since the - // runtimeShadowNodeReference_ is not propagated to nodes cloned - // outside of the JS thread. So when the flag is disabled we - // keep the updates in the registry and we will reapply them in - // a commit hook triggered by a rerender. - if (result == ShadowTree::CommitStatus::Succeeded && - ReactNativeFeatureFlags:: - updateRuntimeShadowNodeReferencesOnCommitThread()) { - if (auto animatedPropsRegistry = - weakAnimatedPropsRegistry.lock()) { - animatedPropsRegistry->clear(shadowTree.getSurfaceId()); - } - } - }); - }); + writeFinalValues( + finalValues, oldRootShadowNode, newRootShadowNode); + return newRootShadowNode; + }, + {.source = ShadowTreeCommitSource::AnimationEndSync}); + if (auto animatedPropsRegistry = weakAnimatedPropsRegistry.lock(); + animatedPropsRegistry && !finalValues.empty() && + result != ShadowTree::CommitStatus::Succeeded) { + animatedPropsRegistry->restoreFinalValues( + shadowTree.getSurfaceId(), std::move(finalValues)); + } + // To clear the registry, the updates neeed to be propagated to + // React with RSNRU. Without + // updateRuntimeShadowNodeReferencesOnCommitThread this won't + // happen if we do any commits on the main thread, since the + // runtimeShadowNodeReference_ is not propagated to nodes cloned + // outside of the JS thread. So when the flag is disabled we + // keep the updates in the registry and we will reapply them in + // a commit hook triggered by a rerender. + if (result == ShadowTree::CommitStatus::Succeeded && + ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommitThread()) { + if (auto animatedPropsRegistry = + weakAnimatedPropsRegistry.lock()) { + animatedPropsRegistry->clear(shadowTree.getSurfaceId()); + } + } + }); + }); } } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index 3c328ff2adb1..6dcf6e9a3245 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -28,6 +28,7 @@ namespace facebook::react { class AnimationBackend; class AnimationBackendCommitHook; class AnimatedPropsRegistry; +struct FrontendMutations; using Callback = std::function; @@ -36,6 +37,11 @@ struct CallbackWithId { Callback callback; }; +struct RegisteredFrontend { + AnimationFrontend frontend; + bool started{false}; +}; + class AnimationBackend : public UIManagerAnimationBackend { public: using ResumeCallback = std::function; @@ -56,6 +62,10 @@ class AnimationBackend : public UIManagerAnimationBackend { void pushAnimationMutations(const Callback &callback) override; CallbackId start(const Callback &callback) override; void stop(CallbackId callbackId) override; + AnimationFrontendId registerFrontend(AnimationFrontend frontend) override; + void startFrontend(AnimationFrontendId frontendId) override; + void stopFrontend(AnimationFrontendId frontendId) override; + void pushAnimationMutations(AnimationFrontendId frontendId, const Callback &callback) override; private: using SurfaceUpdates = std::unordered_map; @@ -68,8 +78,12 @@ class AnimationBackend : public UIManagerAnimationBackend { void applySurfaceUpdates( std::unordered_map &surfaceUpdates, const std::set &asyncFlushSurfaces); - void applyMutations(std::vector batches); + void applyMutations(std::vector batches); + void resumeFramesLocked(); + void pauseFramesIfIdleLocked(); std::vector callbacks; + // Indexed by frontend id - 1. + std::vector frontends_; std::shared_ptr animatedPropsRegistry_; std::shared_ptr animationChoreographer_; std::unique_ptr commitHook_; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h index fb491d290959..d860b2549ed3 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h @@ -22,6 +22,8 @@ struct AnimationMutation { std::shared_ptr family; AnimatedProps props; bool hasLayoutUpdates{false}; + // The last value of an animation of this view. + bool finishesAnimation{false}; }; struct AnimationMutations { diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp index 1c4dee1ea16a..c2a74fe20275 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp @@ -29,20 +29,29 @@ class AnimatedPropsRegistryTest : public ::testing::Test { registry.initializeSurface(1); } - void update(AnimatedProps props) { - std::vector batches(1); - batches[0].batch.push_back( + void update( + AnimatedProps props, + bool commitDirectProps = true, + bool finishesAnimation = false) { + std::vector batches(1); + batches[0].commitDirectProps = commitDirectProps; + batches[0].mutations.batch.push_back( AnimationMutation{ .tag = 10, .family = node->getFamilyShared(), - .props = std::move(props)}); + .props = std::move(props), + .finishesAnimation = finishesAnimation}); registry.update(batches); } - void updateRaw(folly::dynamic props) { + void updateRaw( + folly::dynamic props, + bool commitDirectProps = true, + bool finishesAnimation = false) { update( - {.props = {}, - .rawProps = std::make_unique(std::move(props))}); + {.props = {}, .rawProps = std::make_unique(std::move(props))}, + commitDirectProps, + finishesAnimation); } void expectRawProps(const folly::dynamic& expected) { @@ -54,6 +63,66 @@ class AnimatedPropsRegistryTest : public ::testing::Test { } }; +TEST_F(AnimatedPropsRegistryTest, unsyncedUpdatesAreNotRecorded) { + updateRaw(folly::dynamic::object("opacity", 0.5), false); + auto [families, map] = registry.getMap(1); + EXPECT_TRUE(families.empty()); + EXPECT_FALSE(map.contains(10)); +} + +TEST_F(AnimatedPropsRegistryTest, unsyncedFinalValuesAreKeptApart) { + updateRaw(folly::dynamic::object("opacity", 0.5), false); + updateRaw(folly::dynamic::object("opacity", 0.2), false, true); + + auto [families, map] = registry.getMap(1); + EXPECT_TRUE(families.empty()); + EXPECT_FALSE(map.contains(10)); + auto finalValues = registry.takeFinalValues(1); + ASSERT_TRUE(finalValues.contains(10)); + folly::dynamic expected = folly::dynamic::object("opacity", 0.2); + EXPECT_EQ(finalValues.at(10).props, expected); +} + +TEST_F(AnimatedPropsRegistryTest, takenFinalValuesAreNotTakenAgain) { + updateRaw(folly::dynamic::object("opacity", 0.2), false, true); + registry.takeFinalValues(1); + updateRaw(folly::dynamic::object("width", 10), false, true); + + auto finalValues = registry.takeFinalValues(1); + ASSERT_TRUE(finalValues.contains(10)); + folly::dynamic expected = folly::dynamic::object("width", 10); + EXPECT_EQ(finalValues.at(10).props, expected); +} + +TEST_F(AnimatedPropsRegistryTest, restoredFinalValuesYieldToNewerOnes) { + updateRaw(folly::dynamic::object("opacity", 0.2)("width", 10), false, true); + auto taken = registry.takeFinalValues(1); + updateRaw(folly::dynamic::object("opacity", 0.3), false, true); + + registry.restoreFinalValues(1, std::move(taken)); + + auto finalValues = registry.takeFinalValues(1); + ASSERT_TRUE(finalValues.contains(10)); + folly::dynamic expected = folly::dynamic::object("opacity", 0.3)("width", 10); + EXPECT_EQ(finalValues.at(10).props, expected); +} + +TEST_F(AnimatedPropsRegistryTest, unsyncedUpdatesEraseOlderRecordedValues) { + updateRaw(folly::dynamic::object("opacity", 0.8)("width", 5)); + snapshot(); + updateRaw(folly::dynamic::object("opacity", 0.7)); + updateRaw(folly::dynamic::object("opacity", 0.2), false); + expectRawProps(folly::dynamic::object("width", 5)); +} + +TEST_F(AnimatedPropsRegistryTest, laterSyncedUpdatesAreRecorded) { + updateRaw(folly::dynamic::object("opacity", 0.25)); + snapshot(); + updateRaw(folly::dynamic::object("opacity", 0.5), false); + updateRaw(folly::dynamic::object("opacity", 0.75)); + expectRawProps(folly::dynamic::object("opacity", 0.75)); +} + TEST_F(AnimatedPropsRegistryTest, rawUpdatesDoNotAllocateTypedProps) { for (int frame = 0; frame < 10; frame++) { updateRaw(folly::dynamic::object("opacity", frame / 10.0)); @@ -68,8 +137,8 @@ TEST_F(AnimatedPropsRegistryTest, updatePreservesInputForSynchronousMount) { folly::dynamic rawProps = folly::dynamic::object("opacity", 0.25)( "transform", folly::dynamic::array(folly::dynamic::object("translateX", 10))); - std::vector batches(1); - batches[0].batch.push_back( + std::vector batches(1); + batches[0].mutations.batch.push_back( AnimationMutation{ .tag = 10, .family = node->getFamilyShared(), @@ -77,7 +146,8 @@ TEST_F(AnimatedPropsRegistryTest, updatePreservesInputForSynchronousMount) { .props = {}, .rawProps = std::make_unique(rawProps)}}); registry.update(batches); registry.update(batches); - EXPECT_EQ(batches[0].batch[0].props.rawProps->toDynamic(), rawProps); + EXPECT_EQ( + batches[0].mutations.batch[0].props.rawProps->toDynamic(), rawProps); EXPECT_EQ(*snapshot().rawProps, rawProps); } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimationBackendFrontendTest.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimationBackendFrontendTest.cpp new file mode 100644 index 000000000000..2d142a62f523 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimationBackendFrontendTest.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace { + +constexpr SurfaceId kSurface = 1; +constexpr Tag kView = 10; + +class TestAnimationChoreographer : public AnimationChoreographer { + public: + bool isResumed{false}; + + void resume() override { + isResumed = true; + } + void pause() override { + isResumed = false; + } +}; + +class RecordingUIManagerDelegate : public UIManagerDelegate { + public: + std::unordered_map synchronousUpdates; + + void uiManagerShouldSynchronouslyUpdateViewOnUIThread( + Tag tag, + const folly::dynamic& props) override { + synchronousUpdates.insert_or_assign(tag, props); + } + + void uiManagerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map& /*updates*/) override {} + void uiManagerDidFinishTransaction( + std::shared_ptr /*mountingCoordinator*/, + bool /*mountSynchronously*/) override {} + void uiManagerDidCreateShadowNode(const ShadowNode& /*shadowNode*/) override { + } + void uiManagerDidDispatchCommand( + const std::shared_ptr& /*shadowNode*/, + const std::string& /*commandName*/, + const folly::dynamic& /*args*/) override {} + void uiManagerDidSendAccessibilityEvent( + const std::shared_ptr& /*shadowNode*/, + const std::string& /*eventType*/) override {} + void uiManagerDidSetIsJSResponder( + const std::shared_ptr& /*shadowNode*/, + bool /*isJSResponder*/, + bool /*blockNativeResponder*/) override {} + void uiManagerDidUpdateShadowTree( + const std::unordered_map& /*tagToProps*/) override {} + void uiManagerShouldAddEventListener( + std::shared_ptr /*listener*/) override {} + void uiManagerShouldRemoveEventListener( + const std::shared_ptr& /*listener*/) override {} + void uiManagerDidStartSurface(const ShadowTree& /*shadowTree*/) override {} + void uiManagerDidFinishReactCommit( + const ShadowTree& /*shadowTree*/) override {} + void uiManagerShouldAddOnSurfaceStartCallback( + OnSurfaceStartCallback&& /*callback*/) override {} + void uiManagerDidCaptureViewSnapshot(Tag /*tag*/, SurfaceId /*surfaceId*/) + override {} + void uiManagerDidSetViewSnapshot( + Tag /*sourceTag*/, + Tag /*targetTag*/, + SurfaceId /*surfaceId*/) override {} + void uiManagerDidClearPendingSnapshots() override {} +}; + +// Runs JS tasks when asked. +class TestCallInvoker : public CallInvoker { + public: + std::vector> tasks; + + void invokeAsync(CallFunc&& /*func*/) noexcept override {} + void invokeAsync(std::function&& func) noexcept override { + tasks.push_back(std::move(func)); + } + void invokeSync(CallFunc&& /*func*/) override {} + + void runTasks() { + for (auto& task : std::exchange(tasks, {})) { + task(); + } + } +}; + +class DirectPathFlags : public ReactNativeFeatureFlagsDefaults { + public: + bool enableCxxAnimatedDirectPathInBackend() override { + return true; + } +}; + +} // namespace + +// A view on a running surface whose React opacity is 0.8. +class AnimationBackendFrontendTest : public ::testing::Test { + protected: + std::shared_ptr contextContainer = + std::make_shared(); + ComponentBuilder builder = simpleComponentBuilder(contextContainer); + RecordingUIManagerDelegate delegate; + std::shared_ptr uiManager = std::make_shared( + [](std::function&& /*callback*/) {}, + contextContainer); + std::shared_ptr view; + ShadowTree* shadowTree{nullptr}; + std::shared_ptr choreographer = + std::make_shared(); + std::shared_ptr backend; + std::shared_ptr jsInvoker = + std::make_shared(); + double frameTime{1000}; + + void SetUp() override { + uiManager->setDelegate(&delegate); + view = builder.build( + Element().tag(kView).surfaceId(kSurface).props([] { + auto props = std::make_shared(); + props->opacity = 0.8; + return props; + })); + auto tree = std::make_unique( + kSurface, + LayoutConstraints{}, + LayoutContext{}, + *uiManager, + *contextContainer); + shadowTree = tree.get(); + tree->commit( + [&](const RootShadowNode& oldRootShadowNode) { + return std::static_pointer_cast( + oldRootShadowNode.ShadowNode::clone( + {.children = std::make_shared< + std::vector>>( + std::vector>{view})})); + }, + {}); + uiManager->startSurface( + std::move(tree), "test", folly::dynamic::object, DisplayMode::Visible); + backend = std::make_shared(choreographer, uiManager); + backend->registerJSInvoker(jsInvoker); + } + + void TearDown() override { + backend.reset(); + uiManager->stopSurface(kSurface); + uiManager->setDelegate(nullptr); + ReactNativeFeatureFlags::dangerouslyReset(); + } + + AnimationMutations opacity(double value, bool finishesAnimation = false) + const { + return rawProps( + folly::dynamic::object("opacity", value), finishesAnimation); + } + + AnimationMutations rawProps(folly::dynamic values, bool finishesAnimation) + const { + AnimationMutations mutations; + if (finishesAnimation) { + mutations.asyncFlushSurfaces.insert(kSurface); + } + mutations.batch.push_back( + AnimationMutation{ + .tag = kView, + .family = view->getFamilyShared(), + .props = + {.props = {}, + .rawProps = std::make_unique(std::move(values))}, + .finishesAnimation = finishesAnimation}); + return mutations; + } + + void frame() { + backend->onAnimationFrame(AnimationTimestamp{frameTime += 17}); + } + + static void enableDirectPath() { + ReactNativeFeatureFlags::dangerouslyForceOverride( + std::make_unique()); + } + + // A React commit of the view, by default changing another prop. + void reactCommit( + const folly::dynamic& rawProps = + folly::dynamic::object("nativeID", "react")) { + shadowTree->commit( + [&](const RootShadowNode& oldRootShadowNode) { + return std::static_pointer_cast( + oldRootShadowNode.cloneTree( + view->getFamily(), [&](const ShadowNode& oldShadowNode) { + PropsParserContext context{kSurface, *contextContainer}; + return oldShadowNode.clone( + {.props = + oldShadowNode.getComponentDescriptor().cloneProps( + context, + oldShadowNode.getProps(), + RawProps(rawProps))}); + })); + }, + {.source = ShadowTreeCommitSource::React}); + } + + Float committedOpacity() const { + const auto& child = + shadowTree->getCurrentRevision().rootShadowNode->getChildren().at(0); + return static_cast(*child->getProps()).opacity; + } +}; + +TEST_F(AnimationBackendFrontendTest, aStartedFrontendRunsOncePerFrame) { + int frames = 0; + auto frontendId = + backend->registerFrontend({.onAnimationFrame = [&](AnimationTimestamp) { + frames++; + return AnimationMutations{}; + }}); + backend->startFrontend(frontendId); + backend->startFrontend(frontendId); + frame(); + EXPECT_EQ(frames, 1); + + backend->stopFrontend(frontendId); + backend->stopFrontend(frontendId); + frame(); + EXPECT_EQ(frames, 1); + + backend->startFrontend(frontendId); + frame(); + EXPECT_EQ(frames, 2); +} + +TEST_F(AnimationBackendFrontendTest, framesComeWhileAFrontendIsStarted) { + auto idle = [](AnimationTimestamp) { return AnimationMutations{}; }; + auto first = backend->registerFrontend({.onAnimationFrame = idle}); + auto second = backend->registerFrontend({.onAnimationFrame = idle}); + backend->startFrontend(first); + backend->startFrontend(second); + EXPECT_TRUE(choreographer->isResumed); + backend->stopFrontend(first); + EXPECT_TRUE(choreographer->isResumed); + backend->stopFrontend(second); + EXPECT_FALSE(choreographer->isResumed); + + auto callbackId = backend->start(idle); + backend->startFrontend(first); + backend->stopFrontend(first); + EXPECT_TRUE(choreographer->isResumed); + backend->stop(callbackId); + EXPECT_FALSE(choreographer->isResumed); +} + +TEST_F( + AnimationBackendFrontendTest, + aFrontendWithoutAFrameCallbackIsNotStarted) { + auto frontendId = backend->registerFrontend({}); + backend->startFrontend(frontendId); + EXPECT_FALSE(choreographer->isResumed); + frame(); +} + +TEST_F(AnimationBackendFrontendTest, framesRunInRegistrationOrder) { + auto first = backend->registerFrontend( + {.onAnimationFrame = [&](AnimationTimestamp) { return opacity(0.3); }}); + auto second = backend->registerFrontend( + {.onAnimationFrame = [&](AnimationTimestamp) { return opacity(0.5); }}); + backend->startFrontend(second); + backend->startFrontend(first); + auto legacy = + backend->start([&](AnimationTimestamp) { return opacity(0.7); }); + frame(); + backend->stop(legacy); + + EXPECT_EQ(delegate.synchronousUpdates.at(kView)["opacity"], 0.5); +} + +TEST_F(AnimationBackendFrontendTest, aCommittingFrontendsValueIsStamped) { + auto frontendId = backend->registerFrontend({.commitDirectProps = true}); + backend->pushAnimationMutations( + frontendId, [&](AnimationTimestamp) { return opacity(0.5); }); + + reactCommit(); + + EXPECT_FLOAT_EQ(committedOpacity(), 0.5); +} + +TEST_F(AnimationBackendFrontendTest, anUncommittedFrontendsValueIsNotStamped) { + enableDirectPath(); + auto frontendId = backend->registerFrontend({.commitDirectProps = false}); + backend->pushAnimationMutations( + frontendId, [&](AnimationTimestamp) { return opacity(0.5); }); + + reactCommit(); + + EXPECT_FLOAT_EQ( + delegate.synchronousUpdates.at(kView)["opacity"].asDouble(), 0.5); + EXPECT_FLOAT_EQ(committedOpacity(), 0.8); +} + +TEST_F( + AnimationBackendFrontendTest, + anUncommittedFrontendsFinalValueIsWrittenByTheEndOfAnimationCommit) { + enableDirectPath(); + auto frontendId = backend->registerFrontend({.commitDirectProps = false}); + backend->pushAnimationMutations( + frontendId, [&](AnimationTimestamp) { return opacity(0.5); }); + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(0.2, /* finishesAnimation */ true); + }); + + reactCommit(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.8); + + jsInvoker->runTasks(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.2); +} + +TEST_F( + AnimationBackendFrontendTest, + aFinalValueIsNotWrittenAgainAfterTheEndOfAnimationCommit) { + enableDirectPath(); + auto frontendId = backend->registerFrontend({.commitDirectProps = false}); + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(0.2, /* finishesAnimation */ true); + }); + jsInvoker->runTasks(); + + reactCommit(folly::dynamic::object("opacity", 0.6)); + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return rawProps( + folly::dynamic::object("nativeID", "animated"), + /* finishesAnimation */ true); + }); + jsInvoker->runTasks(); + + EXPECT_FLOAT_EQ(committedOpacity(), 0.6); +} + +TEST_F( + AnimationBackendFrontendTest, + aFinalValueIsNotOverwrittenByAnOlderStampedOne) { + enableDirectPath(); + backend->pushAnimationMutations( + [&](AnimationTimestamp) { return opacity(0.5); }); + reactCommit(); + auto frontendId = backend->registerFrontend({.commitDirectProps = false}); + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(0.2, /* finishesAnimation */ true); + }); + + jsInvoker->runTasks(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.2); + reactCommit(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.2); +} + +TEST_F(AnimationBackendFrontendTest, anUncommittedFrontendsFrameIsNotStamped) { + enableDirectPath(); + auto frontendId = backend->registerFrontend( + {.commitDirectProps = false, + .onAnimationFrame = [&](AnimationTimestamp) { return opacity(0.5); }}); + backend->startFrontend(frontendId); + frame(); + backend->stopFrontend(frontendId); + + reactCommit(); + + EXPECT_FLOAT_EQ(committedOpacity(), 0.8); +} + +TEST_F(AnimationBackendFrontendTest, eachFinishingUpdateRequestsAFlush) { + auto frontendId = backend->registerFrontend({}); + for (double value : {0.5, 0.2}) { + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(value, /* finishesAnimation */ true); + }); + } + + EXPECT_EQ(jsInvoker->tasks.size(), 2); +} + +TEST_F( + AnimationBackendFrontendTest, + oneFlushPerSurfaceIsScheduledWithTheDirectPath) { + enableDirectPath(); + auto frontendId = backend->registerFrontend({.commitDirectProps = false}); + for (double value : {0.5, 0.2}) { + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(value, /* finishesAnimation */ true); + }); + } + + EXPECT_EQ(jsInvoker->tasks.size(), 1); + jsInvoker->runTasks(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.2); + + backend->pushAnimationMutations(frontendId, [&](AnimationTimestamp) { + return opacity(0.4, /* finishesAnimation */ true); + }); + EXPECT_EQ(jsInvoker->tasks.size(), 1); + jsInvoker->runTasks(); + EXPECT_FLOAT_EQ(committedOpacity(), 0.4); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp index 9005250d6063..056b774559d0 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp @@ -589,6 +589,24 @@ TEST_F( EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); } +TEST_F( + SchedulerDelegateInvalidationTest, + EmptySurfaceStartRunsSurfaceStartCallbacks) { + setUp(); + std::vector startedSurfaces; + scheduler_->getUIManager()->addOnSurfaceStartCallback( + [&](const ShadowTree& shadowTree) { + startedSurfaces.push_back(shadowTree.getSurfaceId()); + }); + surfaceHandler_->constraintLayout( + {.layoutDirection = LayoutDirection::LeftToRight}, {}); + + surfaceHandler_->start(); + + EXPECT_EQ(startedSurfaces, std::vector{11}); + surfaceHandler_->stop(); +} + TEST(SchedulerDelegateTest, animatedPropsUseExistingViewUpdatesByDefault) { class PropsDelegate : public RecordingDelegate { public: diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 7eefe4606177..8bdad687b8a3 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -266,7 +266,15 @@ void UIManager::startSurface( void UIManager::startEmptySurface( ShadowTree::Unique&& shadowTree) const noexcept { TraceSection s("UIManager::startEmptySurface"); + auto surfaceId = shadowTree->getSurfaceId(); shadowTreeRegistry_.add(std::move(shadowTree)); + + shadowTreeRegistry_.visit( + surfaceId, [delegate = delegate_](const ShadowTree& shadowTree) { + if (delegate != nullptr) { + delegate->uiManagerDidStartSurface(shadowTree); + } + }); } void UIManager::setSurfaceProps( diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h index d57f1e772ca9..4442729c4f13 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h @@ -23,6 +23,19 @@ struct AnimationMutations; using AnimationTimestamp = std::chrono::duration; using CallbackId = uint64_t; +using AnimationFrontendId = uint32_t; + +// A producer of animation mutations, such as C++ Animated or Reanimated. +struct AnimationFrontend { + // Whether the commit hook writes the frontend's direct (non-layout) values + // into React commits. Without it, only the values of mutations that finish + // an animation are written, by the end-of-animation commit. Can only be off + // with enableCxxAnimatedDirectPathInBackend. + bool commitDirectProps{true}; + // Runs every frame while the frontend is started, after the callbacks passed + // to `start(callback)` and those of frontends registered earlier. + std::function onAnimationFrame; +}; class UIManagerAnimationBackend { public: @@ -31,6 +44,11 @@ class UIManagerAnimationBackend { virtual ~UIManagerAnimationBackend() = default; virtual void onAnimationFrame(AnimationTimestamp timestamp) = 0; + virtual AnimationFrontendId registerFrontend(AnimationFrontend frontend) = 0; + // Starting a started frontend or stopping a stopped one does nothing. + virtual void startFrontend(AnimationFrontendId frontendId) = 0; + virtual void stopFrontend(AnimationFrontendId frontendId) = 0; + virtual void pushAnimationMutations(AnimationFrontendId frontendId, const Callback &callback) = 0; virtual CallbackId start(const Callback &callback) = 0; virtual void stop(CallbackId callbackId) = 0; virtual void clearRegistry(SurfaceId surfaceId) = 0; diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 612c196ff22d..673fc8073f89 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -278,6 +278,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableCxxAnimatedDirectPathInBackend: { + defaultValue: false, + metadata: { + dateAdded: '2026-10-28', + description: + 'With the shared animation backend, C++ Animated keeps its non-layout values out of React commits and commits the final values once per surface when animations end, as it does without the backend.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableDestroyShadowTreeRevisionAsync: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index e5a8120cff62..91a0874eb962 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<<674f975884ae746e067e614523f2bfc3>> + * @generated SignedSource<<1150cbfba442f9611610ebb16ada83ea>> * @flow strict * @noformat */ @@ -70,6 +70,7 @@ export type ReactNativeFeatureFlags = Readonly<{ enableBridgelessArchitecture: Getter, enableCppPropsIteratorSetter: Getter, enableCustomFocusSearchOnClippedElementsAndroid: Getter, + enableCxxAnimatedDirectPathInBackend: Getter, enableDestroyShadowTreeRevisionAsync: Getter, enableDoubleMeasurementFixAndroid: Getter, enableEagerRootViewAttachment: Getter, @@ -306,6 +307,10 @@ export const enableCppPropsIteratorSetter: Getter = createNativeFlagGet * This enables the fabric implementation of focus search so that we can focus clipped elements */ export const enableCustomFocusSearchOnClippedElementsAndroid: Getter = createNativeFlagGetter('enableCustomFocusSearchOnClippedElementsAndroid', true); +/** + * With the shared animation backend, C++ Animated keeps its non-layout values out of React commits and commits the final values once per surface when animations end, as it does without the backend. + */ +export const enableCxxAnimatedDirectPathInBackend: Getter = createNativeFlagGetter('enableCxxAnimatedDirectPathInBackend', false); /** * Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 6fc752bba2a9..012da580de58 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<<2eb3f4a3dca013c51ad2913944855010>> + * @generated SignedSource<<5d4569b70d989505745a77cac8a71402>> * @flow strict * @noformat */ @@ -46,6 +46,7 @@ export interface Spec extends TurboModule { readonly enableBridgelessArchitecture?: () => boolean; readonly enableCppPropsIteratorSetter?: () => boolean; readonly enableCustomFocusSearchOnClippedElementsAndroid?: () => boolean; + readonly enableCxxAnimatedDirectPathInBackend?: () => boolean; readonly enableDestroyShadowTreeRevisionAsync?: () => boolean; readonly enableDoubleMeasurementFixAndroid?: () => boolean; readonly enableEagerRootViewAttachment?: () => boolean; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index e793d6f7de2c..455951f1a928 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -480,6 +480,7 @@ using facebook::react::AndroidSwipeRefreshLayoutComponentDescriptor = facebook:: using facebook::react::AndroidSwipeRefreshLayoutShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::AndroidSwipeRefreshLayoutState = facebook::react::StateData; using facebook::react::AnimationEndCallback = facebook::react::AsyncCallback; +using facebook::react::AnimationFrontendId = uint32_t; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; using facebook::react::BackgroundImage = std::variant; @@ -1302,13 +1303,17 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public AnimationBackend(std::shared_ptr animationChoreographer, std::shared_ptr uiManager); public using PauseCallback = std::function; public using ResumeCallback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) override; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void stop(facebook::react::CallbackId callbackId) override; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void trigger() override; public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); @@ -4240,13 +4245,17 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) = 0; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void stop(facebook::react::CallbackId callbackId) = 0; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void trigger() = 0; public virtual ~UIManagerAnimationBackend() = default; } @@ -5623,6 +5632,11 @@ struct facebook::react::AnimationConfig { public facebook::react::Float springDamping; } +struct facebook::react::AnimationFrontend { + public bool commitDirectProps; + public std::function onAnimationFrame; +} + struct facebook::react::AnimationKeyFrame { public bool generateFinalSyntheticMutations; public bool invalidated; @@ -5637,6 +5651,7 @@ struct facebook::react::AnimationKeyFrame { } struct facebook::react::AnimationMutation { + public bool finishesAnimation; public bool hasLayoutUpdates; public facebook::react::AnimatedProps props; public facebook::react::Tag tag; @@ -6507,6 +6522,11 @@ struct facebook::react::Rect { public void unionInPlace(const facebook::react::Rect& rect) noexcept; } +struct facebook::react::RegisteredFrontend { + public bool started; + public facebook::react::AnimationFrontend frontend; +} + struct facebook::react::SchedulerToolbox { public facebook::react::ComponentRegistryFactory componentRegistryFactory; public facebook::react::EventBeat::Factory eventBeatFactory; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index b05f7665f7da..4cd86e4fa608 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -3264,6 +3264,7 @@ using facebook::react::ActivityIndicatorViewComponentDescriptor = facebook::reac using facebook::react::ActivityIndicatorViewShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::ActivityIndicatorViewState = facebook::react::StateData; using facebook::react::AnimationEndCallback = facebook::react::AsyncCallback; +using facebook::react::AnimationFrontendId = uint32_t; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; using facebook::react::BackgroundImage = std::variant; @@ -3857,13 +3858,17 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public AnimationBackend(std::shared_ptr animationChoreographer, std::shared_ptr uiManager); public using PauseCallback = std::function; public using ResumeCallback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) override; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void stop(facebook::react::CallbackId callbackId) override; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void trigger() override; public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); @@ -6379,13 +6384,17 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) = 0; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void stop(facebook::react::CallbackId callbackId) = 0; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void trigger() = 0; public virtual ~UIManagerAnimationBackend() = default; } @@ -7763,6 +7772,11 @@ struct facebook::react::AnimationConfig { public facebook::react::Float springDamping; } +struct facebook::react::AnimationFrontend { + public bool commitDirectProps; + public std::function onAnimationFrame; +} + struct facebook::react::AnimationKeyFrame { public bool generateFinalSyntheticMutations; public bool invalidated; @@ -7777,6 +7791,7 @@ struct facebook::react::AnimationKeyFrame { } struct facebook::react::AnimationMutation { + public bool finishesAnimation; public bool hasLayoutUpdates; public facebook::react::AnimatedProps props; public facebook::react::Tag tag; @@ -8493,6 +8508,11 @@ struct facebook::react::Rect { public void unionInPlace(const facebook::react::Rect& rect) noexcept; } +struct facebook::react::RegisteredFrontend { + public bool started; + public facebook::react::AnimationFrontend frontend; +} + struct facebook::react::SchedulerToolbox { public facebook::react::ComponentRegistryFactory componentRegistryFactory; public facebook::react::EventBeat::Factory eventBeatFactory; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index 2772d8e63b9e..a9984ce02ac3 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -108,6 +108,7 @@ using facebook::react::EventListenerT = std::function; typedef std::function facebook::react::ValueListenerCallback; using facebook::react::ActivePointerRegistry = std::unordered_map; using facebook::react::AnimationEndCallback = facebook::react::AsyncCallback; +using facebook::react::AnimationFrontendId = uint32_t; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; using facebook::react::BackgroundImage = std::variant; @@ -643,13 +644,17 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public AnimationBackend(std::shared_ptr animationChoreographer, std::shared_ptr uiManager); public using PauseCallback = std::function; public using ResumeCallback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) override; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void stop(facebook::react::CallbackId callbackId) override; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) override; public virtual void trigger() override; public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); @@ -2711,13 +2716,17 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; + public virtual facebook::react::AnimationFrontendId registerFrontend(facebook::react::AnimationFrontend frontend) = 0; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; + public virtual void pushAnimationMutations(facebook::react::AnimationFrontendId frontendId, const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; + public virtual void startFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void stop(facebook::react::CallbackId callbackId) = 0; + public virtual void stopFrontend(facebook::react::AnimationFrontendId frontendId) = 0; public virtual void trigger() = 0; public virtual ~UIManagerAnimationBackend() = default; } @@ -3974,6 +3983,11 @@ struct facebook::react::AnimationConfig { public facebook::react::AnimationType animationType; } +struct facebook::react::AnimationFrontend { + public bool commitDirectProps; + public std::function onAnimationFrame; +} + struct facebook::react::AnimationKeyFrame { public bool generateFinalSyntheticMutations; public bool invalidated; @@ -3988,6 +4002,7 @@ struct facebook::react::AnimationKeyFrame { } struct facebook::react::AnimationMutation { + public bool finishesAnimation; public bool hasLayoutUpdates; public facebook::react::AnimatedProps props; public facebook::react::Tag tag; @@ -4666,6 +4681,11 @@ struct facebook::react::Rect { public void unionInPlace(const facebook::react::Rect& rect) noexcept; } +struct facebook::react::RegisteredFrontend { + public bool started; + public facebook::react::AnimationFrontend frontend; +} + struct facebook::react::SchedulerToolbox { public facebook::react::ComponentRegistryFactory componentRegistryFactory; public facebook::react::EventBeat::Factory eventBeatFactory;