diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 6cc4e5b6feb730..b60e96da05d673 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -78,6 +78,18 @@ void schedulerDidUpdateShadowTree(const std::unordered_map // This delegate method is not currently used on iOS. } + void schedulerShouldResumeAnimationBackend() override + { + // Does nothing. + // This delegate method is not currently used on iOS. + } + + void schedulerShouldPauseAnimationBackend() override + { + // Does nothing. + // This delegate method is not currently used on iOS. + } + private: void *scheduler_; }; diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index dc7447d202c85f..979ec533075f28 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/animationbackend) add_react_common_subdir(react/renderer/animations) add_react_common_subdir(react/renderer/attributedstring) add_react_common_subdir(react/renderer/componentregistry) @@ -200,6 +201,7 @@ add_library(reactnative $ $ $ + $ $ $ $ @@ -293,6 +295,7 @@ target_include_directories(reactnative $ $ $ + $ $ $ $ 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 3b2be76a229812..3451fc67fce66d 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 @@ -764,6 +764,10 @@ void FabricUIManagerBinding::schedulerDidUpdateShadowTree( // no-op } +void FabricUIManagerBinding::schedulerShouldResumeAnimationBackend() {} + +void FabricUIManagerBinding::schedulerShouldPauseAnimationBackend() {} + void FabricUIManagerBinding::onAnimationStarted() { auto mountingManager = getMountingManager("onAnimationStarted"); if (!mountingManager) { 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 29adfde41cebb6..248de755d30378 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 @@ -112,6 +112,10 @@ class FabricUIManagerBinding : public jni::HybridClass, void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; + void schedulerShouldResumeAnimationBackend() override; + + void schedulerShouldPauseAnimationBackend() override; + void setPixelDensity(float pointScaleFactor); void driveCxxAnimations(); diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 17d987fa6bd665..ff8a424531fb60 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -157,6 +157,7 @@ Pod::Spec.new do |s| ss.source_files = podspec_sources("react/renderer/scheduler/**/*.{m,mm,cpp,h}", "react/renderer/scheduler/**/*.h") ss.header_dir = "react/renderer/scheduler" + ss.dependency "React-Fabric/animationbackend" ss.dependency "React-performancecdpmetrics" ss.dependency "React-performancetimeline" ss.dependency "React-Fabric/observers/events" diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 762c5eb6b49e9e..1bc2e563dd2843 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -559,10 +559,8 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) { if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { #ifdef RN_USE_ANIMATION_BACKEND if (auto animationBackend = animationBackend_.lock()) { - std::static_pointer_cast(animationBackend) - ->start( - [this](float /*f*/) { return pullAnimationMutations(); }, - isAsync); + animationBackend->start( + [this](float /*f*/) { return pullAnimationMutations(); }, isAsync); } #endif diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index b0d08ce645fe6c..dcfd6eaa33fcb0 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -88,24 +88,17 @@ NativeAnimatedNodesManagerProvider::getOrCreate( if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { #ifdef RN_USE_ANIMATION_BACKEND - // TODO: this should be initialized outside of animated, but for now it - // was convenient to do it here - animationBackend_ = std::make_shared( - std::move(startOnRenderCallback_), - std::move(stopOnRenderCallback_), - std::move(directManipulationCallback), - std::move(fabricCommitCallback), - uiManager, - jsInvoker); + auto animationBackend = uiManager->unstable_getAnimationBackend().lock(); + react_native_assert( + animationBackend != nullptr && "animationBackend is nullptr"); + animationBackend->registerJSInvoker(jsInvoker); nativeAnimatedNodesManager_ = - std::make_shared(animationBackend_); + std::make_shared(animationBackend); nativeAnimatedDelegate_ = std::make_shared( - animationBackend_); - - uiManager->unstable_setAnimationBackend(animationBackend_); + animationBackend); #endif } else { nativeAnimatedNodesManager_ = diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h index de8263df1ecdfe..1022b69a82d127 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include "NativeAnimatedNodesManager.h" @@ -32,7 +33,6 @@ class NativeAnimatedNodesManagerProvider { std::shared_ptr getEventEmitterListener(); private: - std::shared_ptr animationBackend_; std::shared_ptr nativeAnimatedNodesManager_; std::shared_ptr eventEmitterListenerContainer_; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index 064f6ce53ee8e5..db8e31cc50280d 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -15,15 +15,6 @@ namespace facebook::react { -static const auto layoutProps = std::set{ - WIDTH, HEIGHT, FLEX, MARGIN, PADDING, - POSITION, BORDER_WIDTH, ALIGN_CONTENT, ALIGN_ITEMS, ALIGN_SELF, - ASPECT_RATIO, BOX_SIZING, DISPLAY, FLEX_BASIS, FLEX_DIRECTION, - ROW_GAP, COLUMN_GAP, FLEX_GROW, FLEX_SHRINK, FLEX_WRAP, - JUSTIFY_CONTENT, MAX_HEIGHT, MAX_WIDTH, MIN_HEIGHT, MIN_WIDTH, - STYLE_OVERFLOW, POSITION_TYPE, DIRECTION, Z_INDEX, -}; - UIManagerNativeAnimatedDelegateBackendImpl:: UIManagerNativeAnimatedDelegateBackendImpl( std::weak_ptr animationBackend) @@ -61,20 +52,15 @@ static inline Props::Shared cloneProps( } AnimationBackend::AnimationBackend( - StartOnRenderCallback&& startOnRenderCallback, - StopOnRenderCallback&& stopOnRenderCallback, DirectManipulationCallback&& directManipulationCallback, - FabricCommitCallback&& fabricCommitCallback, - UIManager* uiManager, - std::shared_ptr jsInvoker) - : startOnRenderCallback_(std::move(startOnRenderCallback)), - stopOnRenderCallback_(std::move(stopOnRenderCallback)), - directManipulationCallback_(std::move(directManipulationCallback)), - fabricCommitCallback_(std::move(fabricCommitCallback)), + std::shared_ptr uiManager) + : directManipulationCallback_(std::move(directManipulationCallback)), animatedPropsRegistry_(std::make_shared()), uiManager_(uiManager), - jsInvoker_(std::move(jsInvoker)), - commitHook_(uiManager, animatedPropsRegistry_) {} + commitHook_(uiManager, animatedPropsRegistry_) { + react_native_assert(directManipulationCallback_ != nullptr); + react_native_assert(uiManager_ != nullptr); +} void AnimationBackend::onAnimationFrame(double timestamp) { std::unordered_map surfaceUpdates; @@ -110,21 +96,18 @@ void AnimationBackend::onAnimationFrame(double timestamp) { void AnimationBackend::start(const Callback& callback, bool isAsync) { callbacks.push_back(callback); - // TODO: startOnRenderCallback_ should provide the timestamp from the - // platform - if (startOnRenderCallback_) { - startOnRenderCallback_( - [this]() { - onAnimationFrame( - std::chrono::steady_clock::now().time_since_epoch().count() / - 1000); - }, - isAsync); + if (!isRenderCallbackStarted_) { + auto delegate = uiManager_->getDelegate(); + delegate->uiManagerShouldResumeAnimationBackend(); + isRenderCallbackStarted_ = true; } } + void AnimationBackend::stop(bool isAsync) { - if (stopOnRenderCallback_) { - stopOnRenderCallback_(isAsync); + if (isRenderCallbackStarted_) { + auto delegate = uiManager_->getDelegate(); + delegate->uiManagerShouldPauseAnimationBackend(); + isRenderCallbackStarted_ = false; } callbacks.clear(); } @@ -178,6 +161,9 @@ void AnimationBackend::synchronouslyUpdateProps( void AnimationBackend::requestAsyncFlushForSurfaces( const std::set& surfaces) { + react_native_assert( + jsInvoker_ != nullptr || + surfaces.empty() && "jsInvoker_ was not provided"); for (const auto& surfaceId : surfaces) { // perform an empty commit on the js thread, to force the commit hook to // push updated shadow nodes to react through RSNRU @@ -199,4 +185,11 @@ void AnimationBackend::clearRegistry(SurfaceId surfaceId) { animatedPropsRegistry_->clear(surfaceId); } +void AnimationBackend::registerJSInvoker( + std::shared_ptr jsInvoker) { + if (!jsInvoker_) { + jsInvoker_ = jsInvoker; + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index 28770e02bbee59..6a7c874241b0ce 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -50,36 +50,28 @@ struct AnimationMutations { class AnimationBackend : public UIManagerAnimationBackend { public: using Callback = std::function; - using StartOnRenderCallback = std::function &&, bool /* isAsync */)>; - using StopOnRenderCallback = std::function; + using ResumeCallback = std::function; + using PauseCallback = std::function; using DirectManipulationCallback = std::function; - using FabricCommitCallback = std::function &)>; std::vector callbacks; - const StartOnRenderCallback startOnRenderCallback_; - const StopOnRenderCallback stopOnRenderCallback_; const DirectManipulationCallback directManipulationCallback_; - const FabricCommitCallback fabricCommitCallback_; std::shared_ptr animatedPropsRegistry_; - UIManager *uiManager_; + std::shared_ptr uiManager_; std::shared_ptr jsInvoker_; AnimationBackendCommitHook commitHook_; + bool isRenderCallbackStarted_{false}; - AnimationBackend( - StartOnRenderCallback &&startOnRenderCallback, - StopOnRenderCallback &&stopOnRenderCallback, - DirectManipulationCallback &&directManipulationCallback, - FabricCommitCallback &&fabricCommitCallback, - UIManager *uiManager, - std::shared_ptr jsInvoker); + AnimationBackend(DirectManipulationCallback &&directManipulationCallback, std::shared_ptr uiManager); void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates); void synchronouslyUpdateProps(const std::unordered_map &updates); void requestAsyncFlushForSurfaces(const std::set &surfaces); void clearRegistry(SurfaceId surfaceId) override; + void registerJSInvoker(std::shared_ptr jsInvoker) override; void onAnimationFrame(double timestamp) override; void trigger() override; - void start(const Callback &callback, bool isAsync); + void start(const Callback &callback, bool isAsync) override; void stop(bool isAsync) override; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp index 2058d3d5caa747..495839cee0d415 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp @@ -10,10 +10,11 @@ namespace facebook::react { AnimationBackendCommitHook::AnimationBackendCommitHook( - UIManager* uiManager, + std::shared_ptr uiManager, std::shared_ptr animatedPropsRegistry) - : animatedPropsRegistry_(std::move(animatedPropsRegistry)) { - uiManager->registerCommitHook(*this); + : uiManager_(uiManager), + animatedPropsRegistry_(std::move(animatedPropsRegistry)) { + uiManager_->registerCommitHook(*this); } RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit( @@ -73,4 +74,8 @@ RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit( })); } +AnimationBackendCommitHook::~AnimationBackendCommitHook() { + uiManager_->unregisterCommitHook(*this); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h index 7d6c723d6632c9..bdbc415d033c80 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h @@ -16,10 +16,13 @@ namespace facebook::react { class AnimationBackendCommitHook : public UIManagerCommitHook { + std::shared_ptr uiManager_; std::shared_ptr animatedPropsRegistry_; public: - AnimationBackendCommitHook(UIManager *uiManager, std::shared_ptr animatedPropsRegistry); + AnimationBackendCommitHook( + std::shared_ptr uiManager, + std::shared_ptr animatedPropsRegistry); RootShadowNode::Unshared shadowTreeWillCommit( const ShadowTree &shadowTree, const RootShadowNode::Shared &oldRootShadowNode, @@ -27,6 +30,7 @@ class AnimationBackendCommitHook : public UIManagerCommitHook { const ShadowTreeCommitOptions &commitOptions) noexcept override; void commitHookWasRegistered(const UIManager &uiManager) noexcept override {} void commitHookWasUnregistered(const UIManager &uiManager) noexcept override {} + ~AnimationBackendCommitHook() override; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/animationbackend/CMakeLists.txt index f44356c3ce7ec4..aea055997c5de1 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/CMakeLists.txt @@ -20,7 +20,6 @@ target_link_libraries(react_renderer_animationbackend react_renderer_graphics react_renderer_mounting react_renderer_uimanager - react_renderer_scheduler glog folly_runtime ) diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt index 27263be747403c..2563bc12c6e06f 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(react_renderer_scheduler react_featureflags react_performance_cdpmetrics react_performance_timeline + react_renderer_animationbackend react_renderer_componentregistry react_renderer_core react_renderer_debug @@ -36,3 +37,4 @@ target_link_libraries(react_renderer_scheduler ) target_compile_reactnative_options(react_renderer_scheduler PRIVATE) target_compile_options(react_renderer_scheduler PRIVATE -Wpedantic) +target_compile_definitions(react_renderer_scheduler PRIVATE RN_USE_ANIMATION_BACKEND) diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 8c42afc52964cd..04e8ee103084e8 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -55,6 +55,20 @@ Scheduler::Scheduler( auto uiManager = std::make_shared(runtimeExecutor_, contextContainer_); + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { +#ifdef RN_USE_ANIMATION_BACKEND + auto directManipulationCallback = [delegate]( + Tag tag, + const folly::dynamic& props) { + delegate->schedulerShouldSynchronouslyUpdateViewOnUIThread(tag, props); + }; + + animationBackend_ = std::make_shared( + std::move(directManipulationCallback), uiManager); + uiManager->unstable_setAnimationBackend(animationBackend_); +#endif + } + auto eventOwnerBox = std::make_shared(); eventOwnerBox->owner = eventDispatcher_; @@ -168,6 +182,10 @@ Scheduler::~Scheduler() { uiManager_->unregisterCommitHook(*commitHook); } +#ifdef RN_USE_ANIMATION_BACKEND + animationBackend_ = nullptr; +#endif + // All Surfaces must be explicitly stopped before destroying `Scheduler`. // The idea is that `UIManager` is allowed to call `Scheduler` only if the // corresponding `ShadowTree` instance exists. @@ -355,6 +373,18 @@ void Scheduler::uiManagerShouldRemoveEventListener( removeEventListener(listener); } +void Scheduler::uiManagerShouldResumeAnimationBackend() { + if (delegate_ != nullptr) { + delegate_->schedulerShouldResumeAnimationBackend(); + } +} + +void Scheduler::uiManagerShouldPauseAnimationBackend() { + if (delegate_ != nullptr) { + delegate_->schedulerShouldPauseAnimationBackend(); + } +} + void Scheduler::uiManagerDidStartSurface(const ShadowTree& shadowTree) { std::shared_lock lock(onSurfaceStartCallbackMutex_); if (onSurfaceStartCallback_) { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index c324655da7255a..6d94693adb91e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -13,6 +13,9 @@ #include #include #include +#ifdef RN_USE_ANIMATION_BACKEND +#include +#endif #include #include #include @@ -46,8 +49,6 @@ class Scheduler final : public UIManagerDelegate { /* * Registers and unregisters a `SurfaceHandler` object in the `Scheduler`. - * All registered `SurfaceHandler` objects must be unregistered - * (with the same `Scheduler`) before their deallocation. */ void registerSurface(const SurfaceHandler &surfaceHandler) const noexcept; void unregisterSurface(const SurfaceHandler &surfaceHandler) const noexcept; @@ -97,6 +98,8 @@ class Scheduler final : public UIManagerDelegate { void uiManagerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void uiManagerShouldAddEventListener(std::shared_ptr listener) final; void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) final; + void uiManagerShouldResumeAnimationBackend() override; + void uiManagerShouldPauseAnimationBackend() override; void uiManagerDidStartSurface(const ShadowTree &shadowTree) override; #pragma mark - ContextContainer @@ -148,6 +151,9 @@ class Scheduler final : public UIManagerDelegate { mutable std::shared_mutex onSurfaceStartCallbackMutex_; OnSurfaceStartCallback onSurfaceStartCallback_; +#ifdef RN_USE_ANIMATION_BACKEND + std::shared_ptr animationBackend_; +#endif }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index afa2d57de6574d..7f4cc667691bd8 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -60,6 +60,18 @@ class SchedulerDelegate { virtual void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) = 0; + /* + * Called when the animation backend should start receiving frame callbacks. + * This is used to control the platform-specific animation frame scheduling. + */ + virtual void schedulerShouldResumeAnimationBackend() = 0; + + /* + * Called when the animation backend should stop receiving frame callbacks. + * This is used to pause the platform-specific animation frame scheduling. + */ + virtual void schedulerShouldPauseAnimationBackend() = 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 931bfce1c1e10a..93941a6dd5994f 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -437,8 +437,8 @@ void UIManager::setNativeProps_DEPRECATED( if (family.nativeProps_DEPRECATED) { // Values in `rawProps` patch (take precedence over) // `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED` - // and `rawProps` contain key 'A'. Value from `rawProps` overrides what was - // previously in `nativeProps_DEPRECATED`. + // and `rawProps` contain key 'A'. Value from `rawProps` overrides what + // was previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = std::make_unique(mergeDynamicProps( *family.nativeProps_DEPRECATED, @@ -529,9 +529,9 @@ std::shared_ptr UIManager::findShadowNodeByTag_DEPRECATED( // pointer to a root node because of the possible data race. // To work around this, we ask for a commit and immediately cancel it // returning `nullptr` instead of a new shadow tree. - // We don't want to add a way to access a stored pointer to a root node - // because this `findShadowNodeByTag` is deprecated. It is only added - // to make migration to the new architecture easier. + // We don't want to add a way to access a stored pointer to a root + // node because this `findShadowNodeByTag` is deprecated. It is only + // added to make migration to the new architecture easier. shadowTree.tryCommit( [&](const RootShadowNode& oldRootShadowNode) { rootShadowNode = &oldRootShadowNode; @@ -687,12 +687,14 @@ void UIManager::setNativeAnimatedDelegate( } void UIManager::unstable_setAnimationBackend( - std::weak_ptr animationBackend) { + std::shared_ptr animationBackend) { animationBackend_ = animationBackend; } std::weak_ptr UIManager::unstable_getAnimationBackend() { + react_native_assert( + !animationBackend_.expired() && "Animation Backend was not initialized"); return animationBackend_; } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 794a5559a553fe..cedb993ecd0e76 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -64,7 +64,7 @@ class UIManager final : public ShadowTreeDelegate { /** * Sets and gets UIManager's AnimationBackend reference. */ - void unstable_setAnimationBackend(std::weak_ptr animationBackend); + void unstable_setAnimationBackend(std::shared_ptr animationBackend); std::weak_ptr unstable_getAnimationBackend(); /** diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h index 75b6232810457c..69a3d942c77f64 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h @@ -7,20 +7,27 @@ #pragma once +#include #include #include +#include namespace facebook::react { +struct AnimationMutations; + class UIManagerAnimationBackend { public: + using Callback = std::function; + virtual ~UIManagerAnimationBackend() = default; virtual void onAnimationFrame(double timestamp) = 0; - // TODO: T240293839 Move over start() function and mutation types + virtual void start(const Callback &callback, bool isAsync) = 0; virtual void stop(bool isAsync) = 0; virtual void clearRegistry(SurfaceId surfaceId) = 0; virtual void trigger() = 0; + virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 32df5c92f10992..4fd1477ac4901f 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -79,6 +79,16 @@ class UIManagerDelegate { */ virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) = 0; + /* + * This is used to resume the platform-specific animation frame scheduling. + */ + virtual void uiManagerShouldResumeAnimationBackend() = 0; + + /* + * This is used to pause the platform-specific animation frame scheduling. + */ + virtual void uiManagerShouldPauseAnimationBackend() = 0; + /* * Start surface. */ diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/AnimationChoreographer.h b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/AnimationChoreographer.h new file mode 100644 index 00000000000000..700af8b5febf81 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/AnimationChoreographer.h @@ -0,0 +1,32 @@ +/* + * 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 +namespace facebook::react { + +/* + * This class serves as an interface for native animation frame scheduling that can be used as abstraction in + * ReactCxxPlatform. + */ +class AnimationChoreographer { + public: + virtual ~AnimationChoreographer() = default; + + virtual void resume() = 0; + virtual void pause() = 0; + void setOnAnimationTick(std::function onAnimationTick) + { + onAnimationTick_ = std::move(onAnimationTick); + } + + protected: + std::function onAnimationTick_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp index ad9ca80551be87..6f1962815f99a5 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp @@ -10,8 +10,10 @@ namespace facebook::react { SchedulerDelegateImpl::SchedulerDelegateImpl( - std::shared_ptr mountingManager) noexcept - : mountingManager_(std::move(mountingManager)) {} + std::shared_ptr mountingManager, + std::shared_ptr animationChoreographer) noexcept + : mountingManager_(std::move(mountingManager)), + animationChoreographer_(std::move(animationChoreographer)) {} void SchedulerDelegateImpl::schedulerDidFinishTransaction( const std::shared_ptr& /*mountingCoordinator*/) { @@ -63,4 +65,16 @@ void SchedulerDelegateImpl::schedulerDidUpdateShadowTree( mountingManager_->onUpdateShadowTree(tagToProps); } +void SchedulerDelegateImpl::schedulerShouldResumeAnimationBackend() { + if (animationChoreographer_) { + animationChoreographer_->resume(); + } +} + +void SchedulerDelegateImpl::schedulerShouldPauseAnimationBackend() { + if (animationChoreographer_) { + animationChoreographer_->pause(); + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h index 2167e5fe143121..84a0f692e43f27 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h @@ -9,6 +9,7 @@ #include #include +#include "AnimationChoreographer.h" namespace facebook::react { @@ -16,7 +17,9 @@ class IMountingManager; class SchedulerDelegateImpl : public SchedulerDelegate { public: - SchedulerDelegateImpl(std::shared_ptr mountingManager) noexcept; + SchedulerDelegateImpl( + std::shared_ptr mountingManager, + std::shared_ptr animationChoreographer) noexcept; ~SchedulerDelegateImpl() noexcept override = default; @@ -47,7 +50,12 @@ class SchedulerDelegateImpl : public SchedulerDelegate { void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; + void schedulerShouldResumeAnimationBackend() override; + + void schedulerShouldPauseAnimationBackend() override; + std::shared_ptr mountingManager_; + std::shared_ptr animationChoreographer_; }; }; // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index ca1f38300f3824..9cd7011f355b12 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ struct ReactInstanceData { std::shared_ptr animatedNodesManagerProvider; ReactInstance::BindingsInstallFunc bindingsInstallFunc; + std::shared_ptr animationChoreographer; }; ReactHost::ReactHost( @@ -66,7 +68,8 @@ ReactHost::ReactHost( std::shared_ptr logBoxSurfaceDelegate, std::shared_ptr animatedNodesManagerProvider, - ReactInstance::BindingsInstallFunc bindingsInstallFunc) + ReactInstance::BindingsInstallFunc bindingsInstallFunc, + std::shared_ptr animationChoreographer) : reactInstanceConfig_(std::move(reactInstanceConfig)) { auto componentRegistryFactory = mountingManager->getComponentRegistryFactory(); @@ -82,7 +85,8 @@ ReactHost::ReactHost( .turboModuleProviders = std::move(turboModuleProviders), .logBoxSurfaceDelegate = logBoxSurfaceDelegate, .animatedNodesManagerProvider = animatedNodesManagerProvider, - .bindingsInstallFunc = std::move(bindingsInstallFunc)}); + .bindingsInstallFunc = std::move(bindingsInstallFunc), + .animationChoreographer = std::move(animationChoreographer)}); if (!reactInstanceData_->contextContainer ->find(MessageQueueThreadFactoryKey) .has_value()) { @@ -225,9 +229,26 @@ void ReactHost::createReactInstance() { }; schedulerDelegate_ = std::make_unique( - reactInstanceData_->mountingManager); + reactInstanceData_->mountingManager, + reactInstanceData_->animationChoreographer); scheduler_ = std::make_unique(toolbox, nullptr, schedulerDelegate_.get()); + + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { +#ifdef RN_USE_ANIMATION_BACKEND + if (reactInstanceData_->animationChoreographer) { + std::weak_ptr animationBackend = + scheduler_->getUIManager()->unstable_getAnimationBackend(); + reactInstanceData_->animationChoreographer->setOnAnimationTick( + [animationBackend](float timestamp) { + if (auto strongAnimationBackend = animationBackend.lock()) { + strongAnimationBackend->onAnimationFrame(timestamp); + } + }); + } +#endif + } + surfaceManager_ = std::make_unique(*scheduler_); reactInstanceData_->mountingManager->setSchedulerTaskExecutor( diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.h b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.h index f589d16bb4b361..ec8d2bf0751163 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.h +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,8 @@ class ReactHost { TurboModuleProviders turboModuleProviders = {}, std::shared_ptr logBoxSurfaceDelegate = nullptr, std::shared_ptr animatedNodesManagerProvider = nullptr, - ReactInstance::BindingsInstallFunc bindingsInstallFunc = nullptr); + ReactInstance::BindingsInstallFunc bindingsInstallFunc = nullptr, + std::shared_ptr animationChoreographer = nullptr); ReactHost(const ReactHost &) = delete; ReactHost &operator=(const ReactHost &) = delete; ReactHost(ReactHost &&) noexcept = delete; diff --git a/private/react-native-fantom/tester/src/TesterAnimationChoreographer.cpp b/private/react-native-fantom/tester/src/TesterAnimationChoreographer.cpp new file mode 100644 index 00000000000000..96921de4a24f9e --- /dev/null +++ b/private/react-native-fantom/tester/src/TesterAnimationChoreographer.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TesterAnimationChoreographer.h" +#include +#include +#include + +namespace facebook::react { + +void TesterAnimationChoreographer::resume() { + isPaused_ = false; +} +void TesterAnimationChoreographer::pause() { + isPaused_ = true; +} + +void TesterAnimationChoreographer::runUITick(float timestamp) { + if (!isPaused_ && onAnimationTick_) { + onAnimationTick_(timestamp); + } +} + +} // namespace facebook::react diff --git a/private/react-native-fantom/tester/src/TesterAnimationChoreographer.h b/private/react-native-fantom/tester/src/TesterAnimationChoreographer.h new file mode 100644 index 00000000000000..06cb245220dbd5 --- /dev/null +++ b/private/react-native-fantom/tester/src/TesterAnimationChoreographer.h @@ -0,0 +1,26 @@ +/* + * 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 { + +class TesterAnimationChoreographer : public AnimationChoreographer { + public: + void resume() override; + void pause() override; + void runUITick(float timestamp); + + private: + bool isPaused_{false}; +}; + +} // namespace facebook::react diff --git a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp index 77a6cd17376b36..ecd61319597912 100644 --- a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp +++ b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -109,11 +110,19 @@ TesterAppDelegate::TesterAppDelegate( g_setNativeAnimatedNowTimestampFunction(StubClock::now); - auto provider = std::make_shared( - [this](std::function&& onRender, bool /*isAsync*/) { - onAnimationRender_ = std::move(onRender); - }, - [this](bool /*isAsync*/) { onAnimationRender_ = nullptr; }); + std::shared_ptr provider; + + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + provider = std::make_shared(); + } else { + provider = std::make_shared( + [this](std::function&& onRender, bool /*isAsync*/) { + onAnimationRender_ = std::move(onRender); + }, + [this](bool /*isAsync*/) { onAnimationRender_ = nullptr; }); + } + + animationChoreographer_ = std::make_shared(); reactHost_ = std::make_unique( reactInstanceConfig, @@ -125,7 +134,9 @@ TesterAppDelegate::TesterAppDelegate( nullptr, turboModuleProviders, nullptr, - std::move(provider)); + std::move(provider), + nullptr, + animationChoreographer_); // Ensure that the ReactHost initialisation is completed. // This will call `setupJSNativeFantom`. @@ -253,7 +264,12 @@ void TesterAppDelegate::produceFramesForDuration(double milliseconds) { } void TesterAppDelegate::runUITick() { - if (onAnimationRender_) { + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + auto microseconds = std::chrono::duration_cast( + StubClock::now().time_since_epoch()) + .count(); + animationChoreographer_->runUITick(microseconds / 1000); + } else if (onAnimationRender_) { onAnimationRender_(); } } diff --git a/private/react-native-fantom/tester/src/TesterAppDelegate.h b/private/react-native-fantom/tester/src/TesterAppDelegate.h index b8dc581348d0cd..d754f958570157 100644 --- a/private/react-native-fantom/tester/src/TesterAppDelegate.h +++ b/private/react-native-fantom/tester/src/TesterAppDelegate.h @@ -13,6 +13,7 @@ #include #include +#include "TesterAnimationChoreographer.h" #include "TesterMountingManager.h" namespace facebook::jsi { @@ -72,6 +73,8 @@ class TesterAppDelegate { std::shared_ptr mountingManager_; + std::shared_ptr animationChoreographer_; + private: void runUITick();