From e5a2ae0bb95f6eb254d84a7e8c5c6db997becbaf Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 16 Apr 2026 04:14:28 -0700 Subject: [PATCH 1/2] Add optimizedAnimatedPropUpdates feature flag Summary: Add a new feature flag to gate the optimized animated prop updates code path. Differential Revision: D101157450 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 ++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 70 ++++++++++++------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 +++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 20 files changed, 162 insertions(+), 45 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 43ca6dc4f166..97486e0396c4 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<<99a7d3e814f4b037ed4496b6eee4f264>> + * @generated SignedSource<> */ /** @@ -414,6 +414,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun hideOffscreenVirtualViewsOnIOS(): Boolean = accessor.hideOffscreenVirtualViewsOnIOS() + /** + * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. + */ + @JvmStatic + public fun optimizedAnimatedPropUpdates(): Boolean = accessor.optimizedAnimatedPropUpdates() + /** * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. */ 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 be22235e4470..862fbea4a455 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<<9f7ea26f2a79cb69ec00b8ec763b605e>> */ /** @@ -84,6 +84,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null + private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null @@ -686,6 +687,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun optimizedAnimatedPropUpdates(): Boolean { + var cached = optimizedAnimatedPropUpdatesCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.optimizedAnimatedPropUpdates() + optimizedAnimatedPropUpdatesCache = cached + } + return cached + } + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean { var cached = overrideBySynchronousMountPropsAtMountingAndroidCache 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 2ba162535ac8..4b0de5ad2a08 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<<8667d7237cea82bb5978cb19582d59c0>> + * @generated SignedSource<<615682a04f2be0b548235ca172faa244>> */ /** @@ -156,6 +156,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun hideOffscreenVirtualViewsOnIOS(): Boolean + @DoNotStrip @JvmStatic public external fun optimizedAnimatedPropUpdates(): Boolean + @DoNotStrip @JvmStatic public external fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean @DoNotStrip @JvmStatic public external fun perfIssuesEnabled(): 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 0bd08cd5665c..232e854afb40 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<<17abc72a4045c5695818f254be1783b5>> + * @generated SignedSource<<7b05d0055367981a6c6eb6cd79ef93a6>> */ /** @@ -151,6 +151,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun hideOffscreenVirtualViewsOnIOS(): Boolean = false + override fun optimizedAnimatedPropUpdates(): Boolean = false + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = false override fun perfIssuesEnabled(): 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 b2e5d18cd8df..cd71cdc10ed9 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<<77ba6c5db120016e6e1f8af195ab3690>> + * @generated SignedSource<<9b1e9427538a12e34afe0f045f9adc94>> */ /** @@ -88,6 +88,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null + private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null @@ -754,6 +755,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun optimizedAnimatedPropUpdates(): Boolean { + var cached = optimizedAnimatedPropUpdatesCache + if (cached == null) { + cached = currentProvider.optimizedAnimatedPropUpdates() + accessedFeatureFlags.add("optimizedAnimatedPropUpdates") + optimizedAnimatedPropUpdatesCache = cached + } + return cached + } + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean { var cached = overrideBySynchronousMountPropsAtMountingAndroidCache 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 41ce962f044a..ca3811d6251d 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<<8496c138ce5493df84149940df0de944>> + * @generated SignedSource<<6aacf1c52ec159d7084dc106da017856>> */ /** @@ -151,6 +151,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun hideOffscreenVirtualViewsOnIOS(): Boolean + @DoNotStrip public fun optimizedAnimatedPropUpdates(): Boolean + @DoNotStrip public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean @DoNotStrip public fun perfIssuesEnabled(): 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 c176fb1b7631..7777c8e46bac 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<<5bac13bb6faeffdd3c5eca800f25b96a>> + * @generated SignedSource<<5cafc732183ce679bfc8ebeacccdffb5>> */ /** @@ -423,6 +423,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool optimizedAnimatedPropUpdates() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("optimizedAnimatedPropUpdates"); + return method(javaProvider_); + } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("overrideBySynchronousMountPropsAtMountingAndroid"); @@ -897,6 +903,11 @@ bool JReactNativeFeatureFlagsCxxInterop::hideOffscreenVirtualViewsOnIOS( return ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS(); } +bool JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); +} + bool JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMountingAndroid( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); @@ -1245,6 +1256,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "hideOffscreenVirtualViewsOnIOS", JReactNativeFeatureFlagsCxxInterop::hideOffscreenVirtualViewsOnIOS), + makeNativeMethod( + "optimizedAnimatedPropUpdates", + JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates), makeNativeMethod( "overrideBySynchronousMountPropsAtMountingAndroid", JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMountingAndroid), 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 d02c0855a993..6a701d34eaac 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<<400ee06a74c3147f8f682257fbf49f82>> */ /** @@ -222,6 +222,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool hideOffscreenVirtualViewsOnIOS( facebook::jni::alias_ref); + static bool optimizedAnimatedPropUpdates( + facebook::jni::alias_ref); + static bool overrideBySynchronousMountPropsAtMountingAndroid( 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 43076de959a4..dbad5192bad0 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<3bb0009fd52637e16a1760bb407dfbcb>> */ /** @@ -282,6 +282,10 @@ bool ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS() { return getAccessor().hideOffscreenVirtualViewsOnIOS(); } +bool ReactNativeFeatureFlags::optimizedAnimatedPropUpdates() { + return getAccessor().optimizedAnimatedPropUpdates(); +} + bool ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid() { return getAccessor().overrideBySynchronousMountPropsAtMountingAndroid(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 8ba43c099206..75d6fed7c53e 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<<86b3267ffa68e0f68280957aa54d5041>> + * @generated SignedSource<<95796a46468fa86faadac188ebb7e98a>> */ /** @@ -359,6 +359,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool hideOffscreenVirtualViewsOnIOS(); + /** + * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. + */ + RN_EXPORT static bool optimizedAnimatedPropUpdates(); + /** * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 1f40889efc0d..f20c232080e3 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<<218ab046c336961b8220c48eb0426b7f>> + * @generated SignedSource<<119d7419d9added3824d19a7215acc89>> */ /** @@ -1181,6 +1181,24 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { + auto flagValue = optimizedAnimatedPropUpdates_.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(64, "optimizedAnimatedPropUpdates"); + + flagValue = currentProvider_->optimizedAnimatedPropUpdates(); + optimizedAnimatedPropUpdates_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingAndroid() { auto flagValue = overrideBySynchronousMountPropsAtMountingAndroid_.load(); @@ -1190,7 +1208,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(64, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(65, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1208,7 +1226,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(65, "perfIssuesEnabled"); + markFlagAsAccessed(66, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1226,7 +1244,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(66, "perfMonitorV2Enabled"); + markFlagAsAccessed(67, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1244,7 +1262,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(67, "preparedTextCacheSize"); + markFlagAsAccessed(68, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1262,7 +1280,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(68, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(69, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1280,7 +1298,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(69, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(70, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1298,7 +1316,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(70, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(71, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1316,7 +1334,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(71, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(72, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipToPaddingWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "syncAndroidClipToPaddingWithOverflow"); + markFlagAsAccessed(73, "syncAndroidClipToPaddingWithOverflow"); flagValue = currentProvider_->syncAndroidClipToPaddingWithOverflow(); syncAndroidClipToPaddingWithOverflow_ = flagValue; @@ -1352,7 +1370,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(73, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(74, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1370,7 +1388,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(74, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1388,7 +1406,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(75, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(76, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1406,7 +1424,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(76, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(77, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1424,7 +1442,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(77, "useFabricInterop"); + markFlagAsAccessed(78, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1442,7 +1460,7 @@ bool ReactNativeFeatureFlagsAccessor::useLISAlgorithmInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(78, "useLISAlgorithmInDifferentiator"); + markFlagAsAccessed(79, "useLISAlgorithmInDifferentiator"); flagValue = currentProvider_->useLISAlgorithmInDifferentiator(); useLISAlgorithmInDifferentiator_ = flagValue; @@ -1460,7 +1478,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(79, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(80, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1478,7 +1496,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(80, "useNestedScrollViewAndroid"); + markFlagAsAccessed(81, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1496,7 +1514,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(81, "useSharedAnimatedBackend"); + markFlagAsAccessed(82, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1514,7 +1532,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(82, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(83, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1532,7 +1550,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(83, "useTurboModuleInterop"); + markFlagAsAccessed(84, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "useTurboModules"); + markFlagAsAccessed(85, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1568,7 +1586,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(86, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1586,7 +1604,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(86, "viewCullingOutsetRatio"); + markFlagAsAccessed(87, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1604,7 +1622,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(87, "viewTransitionEnabled"); + markFlagAsAccessed(88, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1622,7 +1640,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(88, "virtualViewPrerenderRatio"); + markFlagAsAccessed(89, "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 7b83ec509d66..9e38a0429b8e 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<> + * @generated SignedSource<> */ /** @@ -96,6 +96,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxNetworkInspectionEnabled(); bool fuseboxScreenshotCaptureEnabled(); bool hideOffscreenVirtualViewsOnIOS(); + bool optimizedAnimatedPropUpdates(); bool overrideBySynchronousMountPropsAtMountingAndroid(); bool perfIssuesEnabled(); bool perfMonitorV2Enabled(); @@ -132,7 +133,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 89> accessedFeatureFlags_; + std::array, 90> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -198,6 +199,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> fuseboxScreenshotCaptureEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; + std::atomic> optimizedAnimatedPropUpdates_; std::atomic> overrideBySynchronousMountPropsAtMountingAndroid_; std::atomic> perfIssuesEnabled_; std::atomic> perfMonitorV2Enabled_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index b6f431af8f0e..5ccc084af418 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<<33fd238aafa83c5a42803d3f11d55944>> + * @generated SignedSource<<4c7bd394bafae107c5b0cecd64e68298>> */ /** @@ -283,6 +283,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool optimizedAnimatedPropUpdates() override { + return false; + } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 0d48c7d05074..e710007863bc 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<<2a95ea2091c8e73816acf12daf5e2408>> + * @generated SignedSource<<0cf9e76957d6ce463656117b807bd650>> */ /** @@ -621,6 +621,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::hideOffscreenVirtualViewsOnIOS(); } + bool optimizedAnimatedPropUpdates() override { + auto value = values_["optimizedAnimatedPropUpdates"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::optimizedAnimatedPropUpdates(); + } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { auto value = values_["overrideBySynchronousMountPropsAtMountingAndroid"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index a6246efc165a..0d727298c5b1 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<7f4f6be367626b03318be03b138c65c5>> */ /** @@ -89,6 +89,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool fuseboxScreenshotCaptureEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; + virtual bool optimizedAnimatedPropUpdates() = 0; virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0; virtual bool perfIssuesEnabled() = 0; virtual bool perfMonitorV2Enabled() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 2e9a936b9e1e..c4e980bf4226 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<<3d49e243422f2c220ab36f3e32a78e38>> + * @generated SignedSource<<420409c2e94ec03cc17c2bfe51f00ab8>> */ /** @@ -364,6 +364,11 @@ bool NativeReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS( return ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS(); } +bool NativeReactNativeFeatureFlags::optimizedAnimatedPropUpdates( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); +} + bool NativeReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 9580cbc99d25..c4918db5f991 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<<3e07a28d13e142ba3c734ca111eb4974>> + * @generated SignedSource<<3ed28527e9fa66651675a92efdcf2761>> */ /** @@ -164,6 +164,8 @@ class NativeReactNativeFeatureFlags bool hideOffscreenVirtualViewsOnIOS(jsi::Runtime& runtime); + bool optimizedAnimatedPropUpdates(jsi::Runtime& runtime); + bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime); bool perfIssuesEnabled(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 4a1ea7a74db7..825502272fce 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -731,6 +731,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + optimizedAnimatedPropUpdates: { + defaultValue: false, + metadata: { + dateAdded: '2026-04-07', + description: + 'When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, overrideBySynchronousMountPropsAtMountingAndroid: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index c225edc521d6..9a117a7f5a79 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<> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -111,6 +111,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxNetworkInspectionEnabled: Getter, fuseboxScreenshotCaptureEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, + optimizedAnimatedPropUpdates: Getter, overrideBySynchronousMountPropsAtMountingAndroid: Getter, perfIssuesEnabled: Getter, perfMonitorV2Enabled: Getter, @@ -458,6 +459,10 @@ export const fuseboxScreenshotCaptureEnabled: Getter = createNativeFlag * Hides offscreen VirtualViews on iOS by setting hidden = YES to avoid extra cost of views */ export const hideOffscreenVirtualViewsOnIOS: Getter = createNativeFlagGetter('hideOffscreenVirtualViewsOnIOS', false); +/** + * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. + */ +export const optimizedAnimatedPropUpdates: Getter = createNativeFlagGetter('optimizedAnimatedPropUpdates', false); /** * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 42ebd77f42e4..649147e139cc 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<<0e66e4ae4407000706cd243ad17aa605>> + * @generated SignedSource<<1e7a698bd4211eb2ee6d31926980018c>> * @flow strict * @noformat */ @@ -89,6 +89,7 @@ export interface Spec extends TurboModule { +fuseboxNetworkInspectionEnabled?: () => boolean; +fuseboxScreenshotCaptureEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; + +optimizedAnimatedPropUpdates?: () => boolean; +overrideBySynchronousMountPropsAtMountingAndroid?: () => boolean; +perfIssuesEnabled?: () => boolean; +perfMonitorV2Enabled?: () => boolean; From 982288098c7b40dfcd3b95ad56288ae46dd4b24c Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 16 Apr 2026 07:08:52 -0700 Subject: [PATCH 2/2] Add batched animated prop update delegate chain Summary: Wire the delegate chain for batched animated property updates through UIManager, Scheduler, and platform-specific SurfaceManagers. iOS and CxxPlatform get no-op stubs. Gated behind the optimizedAnimatedPropUpdates feature flag. Differential Revision: D101157453 --- .../react-native/React/Fabric/RCTScheduler.mm | 7 + .../react/fabric/FabricUIManager.java | 247 ++++++++++++++++++ .../react/fabric/FabricMountingManager.cpp | 27 ++ .../jni/react/fabric/FabricMountingManager.h | 4 + .../react/fabric/FabricUIManagerBinding.cpp | 10 + .../jni/react/fabric/FabricUIManagerBinding.h | 4 + .../animationbackend/AnimatedPropCommands.h | 217 +++++++++++++++ .../react/renderer/scheduler/Scheduler.cpp | 9 + .../react/renderer/scheduler/Scheduler.h | 3 + .../renderer/scheduler/SchedulerDelegate.h | 4 + .../react/renderer/uimanager/UIManager.cpp | 9 + .../react/renderer/uimanager/UIManager.h | 4 + .../renderer/uimanager/UIManagerDelegate.h | 7 + .../scheduler/SchedulerDelegateImpl.cpp | 7 + .../scheduler/SchedulerDelegateImpl.h | 4 + 15 files changed, 563 insertions(+) create mode 100644 packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropCommands.h diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 408ed9545b2d..5c4a2f18eab9 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -79,6 +79,13 @@ void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, [scheduler.delegate schedulerDidSynchronouslyUpdateViewOnUIThread:tag props:props]; } + void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) override + { + // No-op on iOS. Batched animated prop updates are Android-only. + } + void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override { // Does nothing. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 64d9655cba59..bdd8c4f12ae7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -35,6 +35,8 @@ import com.facebook.proguard.annotations.DoNotStripAny; import com.facebook.react.bridge.ColorPropConverter; import com.facebook.react.bridge.GuardedRunnable; +import com.facebook.react.bridge.JavaOnlyArray; +import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.NativeArray; import com.facebook.react.bridge.NativeMap; @@ -811,6 +813,251 @@ public void synchronouslyUpdateViewOnUIThread(final int reactTag, final Readable ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END, null, commitNumber); } + @SuppressWarnings("unused") + @UiThread + @ThreadConfined(UI) + public void synchronouslyUpdateViewBatch(final int[] intBuffer, final double[] doubleBuffer) { + UiThreadUtil.assertOnUiThread(); + + // Decode buffer protocol and apply props per-view. + // For the skeleton implementation, we decode into ReadableMap per-view + // and delegate to the existing synchronouslyUpdateViewOnUIThread path. + int intIdx = 0; + int doubleIdx = 0; + while (intIdx < intBuffer.length) { + int command = intBuffer[intIdx++]; + if (command != 1) { // CMD_START_OF_VIEW + break; + } + int viewTag = intBuffer[intIdx++]; + JavaOnlyMap props = new JavaOnlyMap(); + + while (intIdx < intBuffer.length) { + command = intBuffer[intIdx++]; + if (command == 4) { // CMD_END_OF_VIEW + break; + } + + switch (command) { + case 10: // CMD_OPACITY + case 11: // CMD_ELEVATION + case 12: // CMD_Z_INDEX + case 13: // CMD_SHADOW_OPACITY + case 14: // CMD_SHADOW_RADIUS + props.putDouble(commandToString(command), doubleBuffer[doubleIdx++]); + break; + + case 15: // CMD_BACKGROUND_COLOR + case 16: // CMD_COLOR + case 17: // CMD_TINT_COLOR + case 40: // CMD_BORDER_COLOR + case 41: // CMD_BORDER_TOP_COLOR + case 42: // CMD_BORDER_BOTTOM_COLOR + case 43: // CMD_BORDER_LEFT_COLOR + case 44: // CMD_BORDER_RIGHT_COLOR + case 45: // CMD_BORDER_START_COLOR + case 46: // CMD_BORDER_END_COLOR + props.putInt(commandToString(command), intBuffer[intIdx++]); + break; + + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + { + // Border radius: value in doubleBuffer, unit in intBuffer + double value = doubleBuffer[doubleIdx++]; + int unit = intBuffer[intIdx++]; + if (unit == 202) { // CMD_UNIT_PX + props.putDouble(commandToString(command), value); + } else if (unit == 203) { // CMD_UNIT_PERCENT + props.putString(commandToString(command), value + "%"); + } + break; + } + + case 2: + { // CMD_START_OF_TRANSFORM + JavaOnlyArray transform = new JavaOnlyArray(); + while (intIdx < intBuffer.length) { + int transformCmd = intBuffer[intIdx++]; + if (transformCmd == 3) { // CMD_END_OF_TRANSFORM + props.putArray("transform", transform); + break; + } + String name = transformCommandToString(transformCmd); + switch (transformCmd) { + case 102: + case 103: + case 104: + case 112: + { + // scale, scaleX, scaleY, perspective + double val = doubleBuffer[doubleIdx++]; + JavaOnlyMap entry = new JavaOnlyMap(); + entry.putDouble(name, val); + transform.pushMap(entry); + break; + } + case 100: + case 101: + { + // translateX, translateY + double val = doubleBuffer[doubleIdx++]; + int unitCmd = intBuffer[intIdx++]; + JavaOnlyMap entry = new JavaOnlyMap(); + if (unitCmd == 202) { // PX + entry.putDouble(name, val); + } else { // PERCENT + entry.putString(name, val + "%"); + } + transform.pushMap(entry); + break; + } + case 105: + case 106: + case 107: + case 108: + case 109: + case 110: + { + // rotate, rotateX/Y/Z, skewX/Y + double angle = doubleBuffer[doubleIdx++]; + int unitCmd = intBuffer[intIdx++]; + String unitStr = unitCmd == 200 ? "deg" : "rad"; + JavaOnlyMap entry = new JavaOnlyMap(); + entry.putString(name, angle + unitStr); + transform.pushMap(entry); + break; + } + case 111: + { + // matrix + int size = intBuffer[intIdx++]; + JavaOnlyArray matrix = new JavaOnlyArray(); + for (int m = 0; m < size; m++) { + matrix.pushDouble(doubleBuffer[doubleIdx++]); + } + JavaOnlyMap entry = new JavaOnlyMap(); + entry.putArray(name, matrix); + transform.pushMap(entry); + break; + } + } + } + break; + } + } + } + synchronouslyUpdateViewOnUIThread(viewTag, props); + } + } + + private static String commandToString(int command) { + switch (command) { + case 10: + return "opacity"; + case 11: + return "elevation"; + case 12: + return "zIndex"; + case 13: + return "shadowOpacity"; + case 14: + return "shadowRadius"; + case 15: + return "backgroundColor"; + case 16: + return "color"; + case 17: + return "tintColor"; + case 20: + return "borderRadius"; + case 21: + return "borderTopLeftRadius"; + case 22: + return "borderTopRightRadius"; + case 23: + return "borderTopStartRadius"; + case 24: + return "borderTopEndRadius"; + case 25: + return "borderBottomLeftRadius"; + case 26: + return "borderBottomRightRadius"; + case 27: + return "borderBottomStartRadius"; + case 28: + return "borderBottomEndRadius"; + case 29: + return "borderStartStartRadius"; + case 30: + return "borderStartEndRadius"; + case 31: + return "borderEndStartRadius"; + case 32: + return "borderEndEndRadius"; + case 40: + return "borderColor"; + case 41: + return "borderTopColor"; + case 42: + return "borderBottomColor"; + case 43: + return "borderLeftColor"; + case 44: + return "borderRightColor"; + case 45: + return "borderStartColor"; + case 46: + return "borderEndColor"; + default: + return "unknown"; + } + } + + private static String transformCommandToString(int command) { + switch (command) { + case 100: + return "translateX"; + case 101: + return "translateY"; + case 102: + return "scale"; + case 103: + return "scaleX"; + case 104: + return "scaleY"; + case 105: + return "rotate"; + case 106: + return "rotateX"; + case 107: + return "rotateY"; + case 108: + return "rotateZ"; + case 109: + return "skewX"; + case 110: + return "skewY"; + case 111: + return "matrix"; + case 112: + return "perspective"; + default: + return "unknown"; + } + } + @SuppressLint("NotInvokedPrivateMethod") @SuppressWarnings("unused") @AnyThread diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index 2eb8986cb70c..1225b4a3376d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -1229,6 +1229,33 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread( synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap); } +void FabricMountingManager::synchronouslyUpdateViewBatchOnUIThread( + const std::vector& intBuffer, + const std::vector& doubleBuffer) { + auto env = jni::Environment::current(); + + auto jIntArray = env->NewIntArray(static_cast(intBuffer.size())); + env->SetIntArrayRegion( + jIntArray, 0, static_cast(intBuffer.size()), intBuffer.data()); + + auto jDoubleArray = + env->NewDoubleArray(static_cast(doubleBuffer.size())); + env->SetDoubleArrayRegion( + jDoubleArray, + 0, + static_cast(doubleBuffer.size()), + doubleBuffer.data()); + + static auto synchronouslyUpdateViewBatchJNI = + JFabricUIManager::javaClassStatic() + ->getMethod( + "synchronouslyUpdateViewBatch"); + synchronouslyUpdateViewBatchJNI(javaUIManager_, jIntArray, jDoubleArray); + + env->DeleteLocalRef(jIntArray); + env->DeleteLocalRef(jDoubleArray); +} + void FabricMountingManager::scheduleReactRevisionMerge(SurfaceId surfaceId) { static const auto scheduleReactRevisionMerge = JFabricUIManager::javaClassStatic()->getMethod( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 50c80646783c..1112f40ee182 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -68,6 +68,10 @@ class FabricMountingManager final { void synchronouslyUpdateViewOnUIThread(Tag viewTag, const folly::dynamic &props); + void synchronouslyUpdateViewBatchOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer); + void scheduleReactRevisionMerge(SurfaceId surfaceId); private: diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp index 4abc6b79b379..c91f2e46be6e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp @@ -792,6 +792,16 @@ void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateViewOnUIThread( } } +void FabricUIManagerBinding:: + schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector& intBuffer, + const std::vector& doubleBuffer) { + if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && mountingManager_) { + mountingManager_->synchronouslyUpdateViewBatchOnUIThread( + intBuffer, doubleBuffer); + } +} + void FabricUIManagerBinding::schedulerDidUpdateShadowTree( const std::unordered_map& /*tagToProps*/) { // no-op diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h index 6d129e7ffeaa..ce798b66f4f2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h @@ -115,6 +115,10 @@ class FabricUIManagerBinding : public jni::HybridClass, void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) override; + void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void setPixelDensity(float pointScaleFactor); diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropCommands.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropCommands.h new file mode 100644 index 000000000000..43e3dd34e842 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropCommands.h @@ -0,0 +1,217 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react::animationbackend { + +// Buffer protocol command constants for batched animated prop updates. +// NOTE: Keep in sync with AnimatedPropCommandConstants.kt on the Java side. + +// View delimiters +static constexpr int CMD_START_OF_VIEW = 1; +static constexpr int CMD_START_OF_TRANSFORM = 2; +static constexpr int CMD_END_OF_TRANSFORM = 3; +static constexpr int CMD_END_OF_VIEW = 4; + +// Simple numeric props (value in doubleBuffer) +static constexpr int CMD_OPACITY = 10; +static constexpr int CMD_ELEVATION = 11; +static constexpr int CMD_Z_INDEX = 12; +static constexpr int CMD_SHADOW_OPACITY = 13; +static constexpr int CMD_SHADOW_RADIUS = 14; + +// Color props (value as int in intBuffer) +static constexpr int CMD_BACKGROUND_COLOR = 15; +static constexpr int CMD_COLOR = 16; +static constexpr int CMD_TINT_COLOR = 17; + +// Border radius props (value in doubleBuffer, unit in intBuffer) +static constexpr int CMD_BORDER_RADIUS = 20; +static constexpr int CMD_BORDER_TOP_LEFT_RADIUS = 21; +static constexpr int CMD_BORDER_TOP_RIGHT_RADIUS = 22; +static constexpr int CMD_BORDER_TOP_START_RADIUS = 23; +static constexpr int CMD_BORDER_TOP_END_RADIUS = 24; +static constexpr int CMD_BORDER_BOTTOM_LEFT_RADIUS = 25; +static constexpr int CMD_BORDER_BOTTOM_RIGHT_RADIUS = 26; +static constexpr int CMD_BORDER_BOTTOM_START_RADIUS = 27; +static constexpr int CMD_BORDER_BOTTOM_END_RADIUS = 28; +static constexpr int CMD_BORDER_START_START_RADIUS = 29; +static constexpr int CMD_BORDER_START_END_RADIUS = 30; +static constexpr int CMD_BORDER_END_START_RADIUS = 31; +static constexpr int CMD_BORDER_END_END_RADIUS = 32; + +// Border color props (value as int in intBuffer) +static constexpr int CMD_BORDER_COLOR = 40; +static constexpr int CMD_BORDER_TOP_COLOR = 41; +static constexpr int CMD_BORDER_BOTTOM_COLOR = 42; +static constexpr int CMD_BORDER_LEFT_COLOR = 43; +static constexpr int CMD_BORDER_RIGHT_COLOR = 44; +static constexpr int CMD_BORDER_START_COLOR = 45; +static constexpr int CMD_BORDER_END_COLOR = 46; + +// Transform commands +static constexpr int CMD_TRANSFORM_TRANSLATE_X = 100; +static constexpr int CMD_TRANSFORM_TRANSLATE_Y = 101; +static constexpr int CMD_TRANSFORM_SCALE = 102; +static constexpr int CMD_TRANSFORM_SCALE_X = 103; +static constexpr int CMD_TRANSFORM_SCALE_Y = 104; +static constexpr int CMD_TRANSFORM_ROTATE = 105; +static constexpr int CMD_TRANSFORM_ROTATE_X = 106; +static constexpr int CMD_TRANSFORM_ROTATE_Y = 107; +static constexpr int CMD_TRANSFORM_ROTATE_Z = 108; +static constexpr int CMD_TRANSFORM_SKEW_X = 109; +static constexpr int CMD_TRANSFORM_SKEW_Y = 110; +static constexpr int CMD_TRANSFORM_MATRIX = 111; +static constexpr int CMD_TRANSFORM_PERSPECTIVE = 112; + +// Unit commands +static constexpr int CMD_UNIT_DEG = 200; +static constexpr int CMD_UNIT_RAD = 201; +static constexpr int CMD_UNIT_PX = 202; +static constexpr int CMD_UNIT_PERCENT = 203; + +// Set of prop names that can be applied synchronously via the buffer protocol. +inline const std::unordered_set &getSynchronousPropNames() +{ + static const std::unordered_set names = { + "opacity", + "elevation", + "zIndex", + "shadowOpacity", + "shadowRadius", + "backgroundColor", + "color", + "tintColor", + "borderRadius", + "borderTopLeftRadius", + "borderTopRightRadius", + "borderTopStartRadius", + "borderTopEndRadius", + "borderBottomLeftRadius", + "borderBottomRightRadius", + "borderBottomStartRadius", + "borderBottomEndRadius", + "borderStartStartRadius", + "borderStartEndRadius", + "borderEndStartRadius", + "borderEndEndRadius", + "borderColor", + "borderTopColor", + "borderBottomColor", + "borderLeftColor", + "borderRightColor", + "borderStartColor", + "borderEndColor", + "transform", + }; + return names; +} + +// Maps a prop name string to its command ID. +// Returns std::nullopt if the prop is not supported. +inline std::optional propNameToCommand(const std::string &name) +{ + if (name == "opacity") + return CMD_OPACITY; + if (name == "elevation") + return CMD_ELEVATION; + if (name == "zIndex") + return CMD_Z_INDEX; + if (name == "shadowOpacity") + return CMD_SHADOW_OPACITY; + if (name == "shadowRadius") + return CMD_SHADOW_RADIUS; + if (name == "backgroundColor") + return CMD_BACKGROUND_COLOR; + if (name == "color") + return CMD_COLOR; + if (name == "tintColor") + return CMD_TINT_COLOR; + if (name == "borderRadius") + return CMD_BORDER_RADIUS; + if (name == "borderTopLeftRadius") + return CMD_BORDER_TOP_LEFT_RADIUS; + if (name == "borderTopRightRadius") + return CMD_BORDER_TOP_RIGHT_RADIUS; + if (name == "borderTopStartRadius") + return CMD_BORDER_TOP_START_RADIUS; + if (name == "borderTopEndRadius") + return CMD_BORDER_TOP_END_RADIUS; + if (name == "borderBottomLeftRadius") + return CMD_BORDER_BOTTOM_LEFT_RADIUS; + if (name == "borderBottomRightRadius") + return CMD_BORDER_BOTTOM_RIGHT_RADIUS; + if (name == "borderBottomStartRadius") + return CMD_BORDER_BOTTOM_START_RADIUS; + if (name == "borderBottomEndRadius") + return CMD_BORDER_BOTTOM_END_RADIUS; + if (name == "borderStartStartRadius") + return CMD_BORDER_START_START_RADIUS; + if (name == "borderStartEndRadius") + return CMD_BORDER_START_END_RADIUS; + if (name == "borderEndStartRadius") + return CMD_BORDER_END_START_RADIUS; + if (name == "borderEndEndRadius") + return CMD_BORDER_END_END_RADIUS; + if (name == "borderColor") + return CMD_BORDER_COLOR; + if (name == "borderTopColor") + return CMD_BORDER_TOP_COLOR; + if (name == "borderBottomColor") + return CMD_BORDER_BOTTOM_COLOR; + if (name == "borderLeftColor") + return CMD_BORDER_LEFT_COLOR; + if (name == "borderRightColor") + return CMD_BORDER_RIGHT_COLOR; + if (name == "borderStartColor") + return CMD_BORDER_START_COLOR; + if (name == "borderEndColor") + return CMD_BORDER_END_COLOR; + if (name == "transform") + return CMD_START_OF_TRANSFORM; + return std::nullopt; +} + +// Maps a transform operation name to its command ID. +// Returns std::nullopt if the transform is not supported. +inline std::optional transformNameToCommand(const std::string &name) +{ + if (name == "translateX") + return CMD_TRANSFORM_TRANSLATE_X; + if (name == "translateY") + return CMD_TRANSFORM_TRANSLATE_Y; + if (name == "scale") + return CMD_TRANSFORM_SCALE; + if (name == "scaleX") + return CMD_TRANSFORM_SCALE_X; + if (name == "scaleY") + return CMD_TRANSFORM_SCALE_Y; + if (name == "rotate") + return CMD_TRANSFORM_ROTATE; + if (name == "rotateX") + return CMD_TRANSFORM_ROTATE_X; + if (name == "rotateY") + return CMD_TRANSFORM_ROTATE_Y; + if (name == "rotateZ") + return CMD_TRANSFORM_ROTATE_Z; + if (name == "skewX") + return CMD_TRANSFORM_SKEW_X; + if (name == "skewY") + return CMD_TRANSFORM_SKEW_Y; + if (name == "matrix") + return CMD_TRANSFORM_MATRIX; + if (name == "perspective") + return CMD_TRANSFORM_PERSPECTIVE; + return std::nullopt; +} + +} // namespace facebook::react::animationbackend diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 477ecb8a8820..3086e40734d4 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -357,6 +357,15 @@ void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread( } } +void Scheduler::uiManagerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector& intBuffer, + const std::vector& doubleBuffer) { + if (delegate_ != nullptr) { + delegate_->schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + intBuffer, doubleBuffer); + } +} + void Scheduler::uiManagerDidUpdateShadowTree( const std::unordered_map& tagToProps) { if (delegate_ != nullptr) { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index 00ed0f43ed06..cfa33dc6ab9a 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -93,6 +93,9 @@ class Scheduler final : public UIManagerDelegate { bool isJSResponder, bool blockNativeResponder) override; void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void uiManagerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) override; void uiManagerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void uiManagerShouldAddEventListener(std::shared_ptr listener) final; void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) final; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index fafb5f90f297..0d09fdf7c68d 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -64,6 +64,10 @@ class SchedulerDelegate { virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + virtual void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) = 0; + virtual void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) = 0; virtual ~SchedulerDelegate() noexcept = default; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 72a7e3f8d1ab..1b5d444a660a 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -752,6 +752,15 @@ void UIManager::synchronouslyUpdateViewOnUIThread( } } +void UIManager::synchronouslyUpdateViewBatchOnUIThread( + const std::vector& intBuffer, + const std::vector& doubleBuffer) { + if (delegate_ != nullptr) { + delegate_->uiManagerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + intBuffer, doubleBuffer); + } +} + #pragma mark ContextContainer std::shared_ptr UIManager::getContextContainer() const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 28b8729e3e01..a61295d95818 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -85,6 +85,10 @@ class UIManager final : public ShadowTreeDelegate { void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props); + void synchronouslyUpdateViewBatchOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer); + /* * Provides access to a UIManagerBinding. * The `callback` methods will not be called if the internal pointer to diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 92ee339f0929..70e80b9ff95d 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -64,6 +64,13 @@ class UIManagerDelegate { */ virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + /* + * Batched synchronous view update for animated props. + */ + virtual void uiManagerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) = 0; + /* * Called after updateShadowTree is invoked. */ diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp index dcc1df6a5cdf..1e9f8b4b6b48 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp @@ -71,6 +71,13 @@ void SchedulerDelegateImpl::schedulerShouldSynchronouslyUpdateViewOnUIThread( mountingManager_->synchronouslyUpdateViewOnUIThread(tag, props); } +void SchedulerDelegateImpl:: + schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector& /*intBuffer*/, + const std::vector& /*doubleBuffer*/) { + // No-op on CxxPlatform. Batched animated prop updates are Android-only. +} + void SchedulerDelegateImpl::schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) { mountingManager_->onUpdateShadowTree(tagToProps); diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h index b60f33962ce9..7dcc8cb8bb25 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h @@ -49,6 +49,10 @@ class SchedulerDelegateImpl : public SchedulerDelegate { void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread( + const std::vector &intBuffer, + const std::vector &doubleBuffer) override; + void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; std::shared_ptr mountingManager_;