From e4bdd03a892f4b740cb57b86ec0c21bd886454a7 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Thu, 11 Jun 2026 14:42:18 -0700 Subject: [PATCH] Serve C++ Native Animated from core (non sharedbackend path) (#57179) Summary: ## Changelog: [Internal] - Serve C++ Native Animated from core (non sharedbackend path) Wire the C++ implementation of Native Animated (the `AnimatedModule`) into the framework core so that, when `cxxNativeAnimatedEnabled` is enabled, it is served without per-app wiring. The flag is flipped in a separate diff. Android drives the animated render loop internally, so `DefaultTurboModules` serves `AnimatedModule` whenever `cxxNativeAnimatedEnabled` is on. iOS/macOS need the platform `RCTAnimatedModuleProvider` to drive rendering when the shared animated backend is off; with the shared backend on, `DefaultTurboModules` serves it instead. The default `RCTReactNativeFactory` delegate installs the provider, and a `React-RCTAnimatedModuleProvider` podspec is added for CocoaPods. Reviewed By: christophpurrer Differential Revision: D108197770 --- .../RCTDefaultReactNativeFactoryDelegate.mm | 19 +++++- .../AppDelegate/React-RCTAppDelegate.podspec | 1 + packages/react-native/Package.swift | 11 +++- .../React-RCTAnimatedModuleProvider.podspec | 61 +++++++++++++++++++ .../defaults/DefaultTurboModules.cpp | 6 ++ .../react-native/scripts/react_native_pods.rb | 1 + 6 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec diff --git a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm index c7d2ef7dd10f..e6f77aad652b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm @@ -13,9 +13,16 @@ #import #endif +#import +#import #import -@implementation RCTDefaultReactNativeFactoryDelegate +@implementation RCTDefaultReactNativeFactoryDelegate { + // C++ Native Animated provider, created once on first use (getTurboModule: may be called + // concurrently for different module names). + RCTAnimatedModuleProvider *_animatedModuleProvider; + dispatch_once_t _animatedModuleProviderToken; +} @synthesize dependencyProvider; @@ -89,6 +96,16 @@ - (void)hostDidStart:(RCTHost *)host - (std::shared_ptr)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr)jsInvoker { + // The dedicated provider supplies the platform-driven C++ Animated module only when the shared + // animated backend is off; with it on, DefaultTurboModules serves AnimatedModule instead. + if (!facebook::react::ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + dispatch_once(&_animatedModuleProviderToken, ^{ + _animatedModuleProvider = [RCTAnimatedModuleProvider new]; + }); + if (auto animatedModule = [_animatedModuleProvider getTurboModule:name jsInvoker:jsInvoker]) { + return animatedModule; + } + } return facebook::react::DefaultTurboModules::getTurboModule(name, jsInvoker); } diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index 437a80be92cc..c4322bc2c36b 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -62,6 +62,7 @@ Pod::Spec.new do |s| s.dependency "React-CoreModules" s.dependency "React-RCTFBReactNativeSpec" s.dependency "React-defaultsnativemodule" + s.dependency "React-RCTAnimatedModuleProvider" if use_hermes() s.dependency 'React-hermes' end diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 878001972779..d2c487b8970b 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -626,11 +626,18 @@ let reactRCTVibration = RNTarget( dependencies: [.yoga, .jsi, .reactTurboModuleCore] ) +/// React-RCTAnimatedModuleProvider.podspec +let reactRCTAnimatedModuleProvider = RNTarget( + name: .reactRCTAnimatedModuleProvider, + path: "ReactApple/RCTAnimatedModuleProvider", + dependencies: [.reactNativeDependencies, .jsi, .reactCore, .reactFeatureFlags, .reactFabric, .reactTurboModuleCore, .yoga, .hermesPrebuilt] +) + /// React-RCTAppDelegate.podspec let reactAppDelegate = RNTarget( name: .reactAppDelegate, path: "Libraries/AppDelegate", - dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactRuntime, .reactRCTImage, .reactHermes, .reactCore, .reactFabric, .reactTurboModuleCore, .hermesPrebuilt, .yoga] + dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactRuntime, .reactRCTImage, .reactHermes, .reactCore, .reactFabric, .reactTurboModuleCore, .reactRCTAnimatedModuleProvider, .hermesPrebuilt, .yoga] ) /// React-RCTLinking.podspec @@ -716,6 +723,7 @@ let targets = [ reactViewTransitionNativeModule, reactFeatureflagsNativemodule, reactNativeModuleDom, + reactRCTAnimatedModuleProvider, reactAppDelegate, reactSettings, reactRuntimeExecutor, @@ -895,6 +903,7 @@ extension String { static let reactRCTActionSheet = "React-RCTActionSheet" // Empty target static let reactRCTLinking = "React-RCTLinking" static let reactCoreModules = "React-CoreModules" + static let reactRCTAnimatedModuleProvider = "RCTAnimatedModuleProvider" static let reactTurboModuleBridging = "ReactCommon/turbomodule/bridging" static let reactTurboModuleCore = "ReactCommon/turbomodule/core" static let reactTurboModuleCoreDefaults = "ReactCommon/turbomodule/core/defaults" diff --git a/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec new file mode 100644 index 000000000000..ac0a79b23e21 --- /dev/null +++ b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec @@ -0,0 +1,61 @@ +# 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. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we're presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] != "0" +new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED=1" : "") +other_cflags = "$(inherited) " + new_arch_enabled_flag + js_engine_flags() + +header_search_paths = [ + "$(PODS_TARGET_SRCROOT)/../../ReactCommon", + "$(PODS_ROOT)/Headers/Private/React-Core", + "$(PODS_ROOT)/Headers/Public/ReactCommon", +] + +Pod::Spec.new do |s| + s.name = "React-RCTAnimatedModuleProvider" + s.version = version + s.summary = "Provides the C++ Native Animated TurboModule to React Native apps." + s.homepage = "https://reactnative.dev/" + s.documentation_url = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = podspec_sources("*.{m,mm,h}", "*.h") + s.public_header_files = "*.h" + s.module_name = "RCTAnimatedModuleProvider" + s.header_dir = "RCTAnimatedModuleProvider" + + s.compiler_flags = other_cflags + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => header_search_paths, + "OTHER_CPLUSPLUSFLAGS" => other_cflags, + "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + "DEFINES_MODULE" => "YES" + } + + s.dependency "React-Core" + s.dependency "React-featureflags" + s.dependency "React-Fabric/animated" + + add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"]) + + depend_on_js_engine(s) + add_rn_third_party_dependencies(s) + add_rncore_dependency(s) +end diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 192805b01122..ddc0242c3e66 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -65,7 +65,13 @@ namespace facebook::react { } if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && + // on Android, the render loop for AnimatedModule is driven + // internally whether or not shared backend is enabled; ios uses + // RCTAnimatedModuleProvider.mm to drive the render loop when shared backend + // is off +#ifndef __ANDROID__ ReactNativeFeatureFlags::useSharedAnimatedBackend() && +#endif name == AnimatedModule::kModuleName) { return std::make_shared( jsInvoker, std::make_shared()); diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 3944c19d1a4e..f9dac08218be 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -167,6 +167,7 @@ def use_react_native! ( pod 'React-jsi', :path => "#{prefix}/ReactCommon/jsi" pod 'RCTSwiftUI', :path => "#{prefix}/ReactApple/RCTSwiftUI" pod 'RCTSwiftUIWrapper', :path => "#{prefix}/ReactApple/RCTSwiftUIWrapper" + pod 'React-RCTAnimatedModuleProvider', :path => "#{prefix}/ReactApple/RCTAnimatedModuleProvider" if hermes_enabled setup_hermes!(:react_native_path => prefix)