From 986559cb0d1579d8bffb277f8f5906bce26df615 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 4 Mar 2026 06:11:07 -0800 Subject: [PATCH] Add c++ AnimatedModule to DefaultTurboModules (#55729) Summary: This is added so that one can easily enables c++ AnimatedModule in open source. If an app doesn't use `RCTAnimatedModuleProvider`(ios) or `AnimatedCxxReactPackage`(android), it can fallback to this default AnimatedModule when it has both c++animated and shared backend enabled - shared backend removes the need to pass down start/stop callbacks to NativeAnimatedNodesManagerProvider, so we can cleanly initialize it as static default - RCTAnimatedModuleProvider uses the version of AnimatedModule that still relies on a dedicated CADisplayLink for start/stop - AnimatedCxxReactPackage also bundles internal ViewEventModule (for NativeViewEvents) that shares `NativeAnimatedNodesManagerProvider` with AnimatedModule, but NativeViewEvents is not needed for open source - Alternatively we could also expose `NativeAnimatedNodesManagerProvider` via UIManager so other turbomodules can also use it. However I don't think it makes sense to double down on another animation API on UIManager given we have shared backend. - This assumes DefaultTurboModules is always the fallback module provider. So it'll not override when app already uses RCTAnimatedModuleProvider or AnimatedCxxReactPackage ## Changelog: [General] [Added] - Add c++ AnimatedModule to DefaultTurboModules Reviewed By: NickGerleman Differential Revision: D94244698 --- packages/react-native/Package.swift | 3 +- .../ReactAndroid/src/main/jni/CMakeLists.txt | 3 + .../RCTAnimatedModuleProvider.mm | 5 +- .../nativemodule/defaults/CMakeLists.txt | 1 + .../defaults/DefaultTurboModules.cpp | 8 + .../React-defaultsnativemodule.podspec | 1 + .../NativeAnimatedNodesManagerProvider.cpp | 150 +++++++++--------- .../react/runtime/TurboModuleManager.cpp | 12 +- .../tester/src/TesterAppDelegate.cpp | 4 +- 9 files changed, 107 insertions(+), 80 deletions(-) diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 852fd7388f91..8c9765260c13 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -424,6 +424,7 @@ let reactFabric = RNTarget( name: .reactFabric, path: "ReactCommon/react/renderer", excludedPaths: [ + "animated/tests", "animations/tests", "attributedstring/tests", "core/tests", @@ -456,7 +457,7 @@ let reactFabric = RNTarget( "components/root/tests", ], dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga], - sources: ["animationbackend", "animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/view/platform/cxx", "components/scrollview", "components/scrollview/platform/cxx", "components/scrollview/platform/ios", "components/legacyviewmanagerinterop", "components/legacyviewmanagerinterop/platform/ios", "dom", "scheduler", "mounting", "observers/events", "observers/intersection", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency", "viewtransition"] + sources: ["animated", "animationbackend", "animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/view/platform/cxx", "components/scrollview", "components/scrollview/platform/cxx", "components/scrollview/platform/ios", "components/legacyviewmanagerinterop", "components/legacyviewmanagerinterop/platform/ios", "dom", "scheduler", "mounting", "observers/events", "observers/intersection", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency", "viewtransition"] ) let reactFabricInputAccessory = RNTarget( diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 21e895e7835f..900792267885 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -82,6 +82,7 @@ add_react_common_subdir(react/debug) add_react_common_subdir(react/featureflags) add_react_common_subdir(react/performance/cdpmetrics) add_react_common_subdir(react/performance/timeline) +add_react_common_subdir(react/renderer/animated) add_react_common_subdir(react/renderer/animationbackend) add_react_common_subdir(react/renderer/animations) add_react_common_subdir(react/renderer/attributedstring) @@ -202,6 +203,7 @@ add_library(reactnative $ $ $ + $ $ $ $ @@ -297,6 +299,7 @@ target_include_directories(reactnative $ $ $ + $ $ $ $ diff --git a/packages/react-native/ReactApple/RCTAnimatedModuleProvider/RCTAnimatedModuleProvider.mm b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/RCTAnimatedModuleProvider.mm index 17914749a512..5cc907bacc23 100644 --- a/packages/react-native/ReactApple/RCTAnimatedModuleProvider/RCTAnimatedModuleProvider.mm +++ b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/RCTAnimatedModuleProvider.mm @@ -70,7 +70,10 @@ - (void)_onDisplayLinkTick - (std::shared_ptr)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr)jsInvoker { - if (facebook::react::ReactNativeFeatureFlags::cxxNativeAnimatedEnabled()) { + if (facebook::react::ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && + // initialization is moved to DefaultTurboModules when using shared animated backend + // TODO: T257053961 deprecate RCTAnimatedModuleProvider. + !facebook::react::ReactNativeFeatureFlags::useSharedAnimatedBackend()) { if (name == facebook::react::AnimatedModule::kModuleName) { __weak RCTAnimatedModuleProvider *weakSelf = self; auto provider = std::make_shared( diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt index 42bcbe2724ff..9f4ce619d652 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(react_nativemodule_defaults react_nativemodule_idlecallbacks react_nativemodule_intersectionobserver react_nativemodule_webperformance + react_renderer_animated ) target_compile_reactnative_options(react_nativemodule_defaults PRIVATE) target_compile_options(react_nativemodule_defaults PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 3c50a17e2ff7..b433364f8a11 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY #include @@ -49,6 +50,13 @@ namespace facebook::react { } } + if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && + ReactNativeFeatureFlags::useSharedAnimatedBackend() && + name == AnimatedModule::kModuleName) { + return std::make_shared( + jsInvoker, std::make_shared()); + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY if (name == DevToolsRuntimeSettingsModule::kModuleName) { return std::make_shared(jsInvoker); diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index cacd372e6ff1..044f5728a71d 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -54,6 +54,7 @@ Pod::Spec.new do |s| s.dependency "React-idlecallbacksnativemodule" s.dependency "React-intersectionobservernativemodule" s.dependency "React-webperformancenativemodule" + s.dependency "React-Fabric/animated" add_dependency(s, "React-RCTFBReactNativeSpec") add_dependency(s, "React-featureflags") add_dependency(s, "React-featureflagsnativemodule") diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index 7d95dcee3b51..95d3a508521e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -45,8 +45,16 @@ std::shared_ptr NativeAnimatedNodesManagerProvider::getOrCreate( jsi::Runtime& runtime, std::shared_ptr jsInvoker) { - if (nativeAnimatedNodesManager_ == nullptr) { - auto* uiManager = &UIManagerBinding::getBinding(runtime)->getUIManager(); + if (nativeAnimatedNodesManager_ != nullptr) { + return nativeAnimatedNodesManager_; + } + + auto* uiManager = &UIManagerBinding::getBinding(runtime)->getUIManager(); + + if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + // === PATH 1: Legacy Backend (useSharedAnimatedBackend = false) === + // Uses the architecture with MergedValueDispatcher and + // AnimatedMountingOverrideDelegate mergedValueDispatcher_ = std::make_unique( [jsInvoker](std::function&& func) { @@ -84,78 +92,78 @@ NativeAnimatedNodesManagerProvider::getOrCreate( } }; - if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - auto animationBackend = uiManager->unstable_getAnimationBackend().lock(); - react_native_assert( - animationBackend != nullptr && "animationBackend is nullptr"); - animationBackend->registerJSInvoker(jsInvoker); - - nativeAnimatedNodesManager_ = - std::make_shared(animationBackend); - } else { - nativeAnimatedNodesManager_ = - std::make_shared( - std::move(directManipulationCallback), - std::move(fabricCommitCallback), - std::move(resolvePlatformColor), - std::move(startOnRenderCallback_), - std::move(stopOnRenderCallback_), - std::move(frameRateListenerCallback_)); - - nativeAnimatedDelegate_ = - std::make_shared( - nativeAnimatedNodesManager_); - } - - addEventEmitterListener( - nativeAnimatedNodesManager_->getEventEmitterListener()); - - uiManager->addEventListener( - std::make_shared( - [eventEmitterListenerContainerWeak = - std::weak_ptr( - eventEmitterListenerContainer_)]( - const RawEvent& rawEvent) { - const auto& eventTarget = rawEvent.eventTarget; - const auto& eventPayload = rawEvent.eventPayload; - if (eventTarget && eventPayload) { - if (auto eventEmitterListenerContainer = - eventEmitterListenerContainerWeak.lock(); - eventEmitterListenerContainer != nullptr) { - return eventEmitterListenerContainer->willDispatchEvent( - eventTarget->getTag(), rawEvent.type, *eventPayload); - } - } - return false; - })); + nativeAnimatedNodesManager_ = std::make_shared( + std::move(directManipulationCallback), + std::move(fabricCommitCallback), + std::move(resolvePlatformColor), + std::move(startOnRenderCallback_), + std::move(stopOnRenderCallback_), + std::move(frameRateListenerCallback_)); + + nativeAnimatedDelegate_ = + 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); + }); - uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_); + // Register on surfaces started in the future + uiManager->setOnSurfaceStartCallback( + [animatedMountingOverrideDelegate = + std::weak_ptr( + animatedMountingOverrideDelegate_)]( + const ShadowTree& shadowTree) { + shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( + animatedMountingOverrideDelegate); + }); - if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - 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->setOnSurfaceStartCallback( - [animatedMountingOverrideDelegate = - std::weak_ptr( - animatedMountingOverrideDelegate_)]( - const ShadowTree& shadowTree) { - shadowTree.getMountingCoordinator()->setMountingOverrideDelegate( - animatedMountingOverrideDelegate); - }); - } + uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_); + } else { + // === PATH 2: Shared AnimationBackend (useSharedAnimatedBackend = true) === + // Uses the shared AnimationBackend from UIManager. The backend handles all + // animation commits and platform integration internally. + + auto animationBackend = uiManager->unstable_getAnimationBackend().lock(); + react_native_assert( + animationBackend != nullptr && "animationBackend is nullptr"); + animationBackend->registerJSInvoker(jsInvoker); + + nativeAnimatedNodesManager_ = + std::make_shared(animationBackend); } + + addEventEmitterListener( + nativeAnimatedNodesManager_->getEventEmitterListener()); + + uiManager->addEventListener( + std::make_shared( + [eventEmitterListenerContainerWeak = + std::weak_ptr( + eventEmitterListenerContainer_)](const RawEvent& rawEvent) { + const auto& eventTarget = rawEvent.eventTarget; + const auto& eventPayload = rawEvent.eventPayload; + if (eventTarget && eventPayload) { + if (auto eventEmitterListenerContainer = + eventEmitterListenerContainerWeak.lock(); + eventEmitterListenerContainer != nullptr) { + return eventEmitterListenerContainer->willDispatchEvent( + eventTarget->getTag(), rawEvent.type, *eventPayload); + } + } + return false; + })); + return nativeAnimatedNodesManager_; } diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/TurboModuleManager.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/TurboModuleManager.cpp index 0b33212a6dd6..ecb9b0b9d05b 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/TurboModuleManager.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/TurboModuleManager.cpp @@ -62,15 +62,19 @@ std::shared_ptr TurboModuleManager::operator()( } } + if (animatedNodesManagerProvider_ != nullptr && + name == AnimatedModule::kModuleName) { + // when animatedNodesManagerProvider_ is null, defer to default + return std::make_shared( + jsInvoker_, animatedNodesManagerProvider_); + } + if (auto turboModule = DefaultTurboModules::getTurboModule(name, jsInvoker_)) { return turboModule; } - if (name == AnimatedModule::kModuleName) { - return std::make_shared( - jsInvoker_, animatedNodesManagerProvider_); - } else if (name == AppStateModule::kModuleName) { + if (name == AppStateModule::kModuleName) { return std::make_shared(jsInvoker_); } else if (name == DeviceInfoModule::kModuleName) { return std::make_shared(jsInvoker_); diff --git a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp index ac0ad0713e68..1fad3777e2f1 100644 --- a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp +++ b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp @@ -112,9 +112,7 @@ TesterAppDelegate::TesterAppDelegate( std::shared_ptr provider; - if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - provider = std::make_shared(); - } else { + if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) { provider = std::make_shared( [this](std::function&& onRender, bool /*isAsync*/) { onAnimationRender_ = std::move(onRender);