diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 6cc4e5b6feb730..a53eb997a1eef9 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -7,6 +7,7 @@ #import "RCTScheduler.h" +#import #import #import #import @@ -113,6 +114,52 @@ void activityDidChange(const RunLoopObserver::Delegate *delegate, RunLoopObserve void *scheduler_; }; +@interface RCTAnimationChoreographerDisplayLinkTarget : NSObject +@property (nonatomic, assign) AnimationChoreographer *choreographer; +- (void)displayLinkTick:(CADisplayLink *)sender; +@end + +@implementation RCTAnimationChoreographerDisplayLinkTarget +- (void)displayLinkTick:(CADisplayLink *)sender +{ + if (_choreographer != nullptr) { + _choreographer->onAnimationFrame(static_cast(sender.targetTimestamp * 1000)); + } +} +@end + +class RCTAnimationChoreographer : public AnimationChoreographer { + CADisplayLink *_animationDisplayLink; + RCTAnimationChoreographerDisplayLinkTarget *_displayLinkTarget; + + public: + RCTAnimationChoreographer() : _displayLinkTarget([[RCTAnimationChoreographerDisplayLinkTarget alloc] init]) + { + _displayLinkTarget.choreographer = this; + } + ~RCTAnimationChoreographer() override + { + if (_animationDisplayLink != nil) { + [_animationDisplayLink invalidate]; + _animationDisplayLink = nil; + } + _displayLinkTarget.choreographer = nullptr; + } + void resume() override + { + if (_animationDisplayLink == nil) { + _animationDisplayLink = [CADisplayLink displayLinkWithTarget:_displayLinkTarget + selector:@selector(displayLinkTick:)]; + [_animationDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + } + [_animationDisplayLink setPaused:NO]; + } + void pause() override + { + [_animationDisplayLink setPaused:YES]; + } +}; + @implementation RCTScheduler { std::unique_ptr _scheduler; std::shared_ptr _animationDriver; @@ -137,6 +184,10 @@ - (instancetype)initWithToolbox:(SchedulerToolbox)toolbox _uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get()); } + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + toolbox.animationChoreographer = std::make_shared(); + } + _scheduler = std::make_unique( toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy.get()); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/AnimationBackendChoreographer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/AnimationBackendChoreographer.kt new file mode 100644 index 00000000000000..1684b77fbcb8a9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/AnimationBackendChoreographer.kt @@ -0,0 +1,85 @@ +/* + * 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. + */ + +package com.facebook.react.fabric + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.modules.core.ReactChoreographer +import com.facebook.react.uimanager.GuardedFrameCallback +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.synchronized + +internal fun interface AnimationFrameCallback { + fun onAnimationFrame(frameTimeMs: Double) +} + +internal class AnimationBackendChoreographer( + reactApplicationContext: ReactApplicationContext, +) { + + var frameCallback: AnimationFrameCallback? = null + private var lastFrameTimeMs: Double = 0.0 + private val reactChoreographer: ReactChoreographer = ReactChoreographer.getInstance() + private val choreographerCallback: GuardedFrameCallback = + object : GuardedFrameCallback(reactApplicationContext) { + override fun doFrameGuarded(frameTimeNanos: Long) { + executeFrameCallback(frameTimeNanos) + } + } + private val callbackPosted: AtomicBoolean = AtomicBoolean() + private val paused: AtomicBoolean = AtomicBoolean(true) + + /* + * resume() and pause() should be called with the same lock to avoid race conditions. + */ + + fun resume() { + if (paused.getAndSet(false)) { + scheduleCallback() + } + } + + fun pause() { + synchronized(paused) { + if (!paused.getAndSet(true) && callbackPosted.getAndSet(false)) { + reactChoreographer.removeFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, + choreographerCallback, + ) + } + } + } + + private fun scheduleCallback() { + synchronized(paused) { + if (!paused.get() && !callbackPosted.getAndSet(true)) { + reactChoreographer.postFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, + choreographerCallback, + ) + } + } + } + + private fun executeFrameCallback(frameTimeNanos: Long) { + callbackPosted.set(false) + val currentFrameTimeMs = calculateTimestamp(frameTimeNanos) + // It is possible for ChoreographerCallback to be executed twice within the same frame + // due to frame drops. If this occurs, the additional callback execution should be ignored. + if (currentFrameTimeMs > lastFrameTimeMs) { + frameCallback?.onAnimationFrame(currentFrameTimeMs) + } + + lastFrameTimeMs = currentFrameTimeMs + scheduleCallback() + } + + private fun calculateTimestamp(frameTimeNanos: Long): Double { + val nanosecondsInMilliseconds = 1000000.0 + return frameTimeNanos / nanosecondsInMilliseconds + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt index 55818f21103314..ac8e4e2ef07e07 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt @@ -79,18 +79,30 @@ internal class FabricUIManagerBinding : HybridClassBase() { external fun driveCxxAnimations() + external fun driveAnimationBackend(frameTimeMs: Double) + external fun drainPreallocateViewsQueue() external fun reportMount(surfaceId: Int) + external fun setAnimationBackendChoreographer( + animationBackendChoreographer: AnimationBackendChoreographer + ) + fun register( runtimeExecutor: RuntimeExecutor, runtimeScheduler: RuntimeScheduler, fabricUIManager: FabricUIManager, eventBeatManager: EventBeatManager, componentFactory: ComponentFactory, + animationBackendChoreographer: AnimationBackendChoreographer, ) { fabricUIManager.setBinding(this) + animationBackendChoreographer.frameCallback = AnimationFrameCallback { frameTimeMs: Double -> + driveAnimationBackend(frameTimeMs) + } + setAnimationBackendChoreographer(animationBackendChoreographer) + installFabricUIManager( runtimeExecutor, runtimeScheduler, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerProviderImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerProviderImpl.kt index 8bcd7e658e0ca4..a0a81ff1fa3877 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerProviderImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerProviderImpl.kt @@ -58,6 +58,8 @@ public class FabricUIManagerProviderImpl( val runtimeExecutor = catalystInstance?.runtimeExecutor val runtimeScheduler = catalystInstance?.runtimeScheduler + val animationBackendChoreographer = AnimationBackendChoreographer(context) + if (runtimeExecutor != null && runtimeScheduler != null) { binding.register( runtimeExecutor, @@ -65,6 +67,7 @@ public class FabricUIManagerProviderImpl( fabricUIManager, eventBeatManager, componentFactory, + animationBackendChoreographer, ) } else { throw IllegalStateException( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt index 4999f867050426..6601c86c8b293c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt @@ -43,6 +43,7 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.devsupport.InspectorFlags.getIsProfilingBuild import com.facebook.react.devsupport.StackTraceHelper import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.fabric.AnimationBackendChoreographer import com.facebook.react.fabric.ComponentFactory import com.facebook.react.fabric.FabricUIManager import com.facebook.react.fabric.FabricUIManagerBinding @@ -250,6 +251,8 @@ internal class ReactInstance( // Misc initialization that needs to be done before Fabric init DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context) + val animationBackendChoreographer = AnimationBackendChoreographer(context) + val binding = FabricUIManagerBinding() binding.register( getBufferedRuntimeExecutor(), @@ -257,6 +260,7 @@ internal class ReactInstance( fabricUIManager, eventBeatManager, componentFactory, + animationBackendChoreographer, ) // Initialize the FabricUIManager 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/AndroidAnimationChoreographer.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AndroidAnimationChoreographer.h new file mode 100644 index 00000000000000..b4cc3a4ced40a9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AndroidAnimationChoreographer.h @@ -0,0 +1,38 @@ +/* + * 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 "JAnimationBackendChoreographer.h" + +namespace facebook::react { + +class AndroidAnimationChoreographer : public AnimationChoreographer { + public: + explicit AndroidAnimationChoreographer(jni::alias_ref jChoreographer) + : jChoreographer_(jni::make_global(jChoreographer)) + { + } + + void resume() override + { + jChoreographer_->resume(); + } + + void pause() override + { + jChoreographer_->pause(); + } + + private: + jni::global_ref jChoreographer_; +}; + +} // namespace facebook::react 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..b11e09664d572f 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 @@ -7,6 +7,7 @@ #include "FabricUIManagerBinding.h" +#include "AndroidAnimationChoreographer.h" #include "AndroidEventBeat.h" #include "ComponentFactory.h" #include "EventBeatManager.h" @@ -54,6 +55,10 @@ void FabricUIManagerBinding::driveCxxAnimations() { getScheduler()->animationTick(); } +void FabricUIManagerBinding::driveAnimationBackend(jdouble frameTimeMs) { + animationChoreographer_->onAnimationFrame(static_cast(frameTimeMs)); +} + void FabricUIManagerBinding::drainPreallocateViewsQueue() { auto mountingManager = getMountingManager("drainPreallocateViewsQueue"); if (!mountingManager) { @@ -572,6 +577,11 @@ void FabricUIManagerBinding::installFabricUIManager( toolbox.eventBeatFactory = eventBeatFactory; + react_native_assert( + animationChoreographer_ != nullptr && + "AnimationChoreographer is nullptr"); + toolbox.animationChoreographer = animationChoreographer_; + animationDriver_ = std::make_shared( runtimeExecutor, contextContainer, this); scheduler_ = @@ -797,6 +807,9 @@ void FabricUIManagerBinding::registerNatives() { "setPixelDensity", FabricUIManagerBinding::setPixelDensity), makeNativeMethod( "driveCxxAnimations", FabricUIManagerBinding::driveCxxAnimations), + makeNativeMethod( + "driveAnimationBackend", + FabricUIManagerBinding::driveAnimationBackend), makeNativeMethod( "drainPreallocateViewsQueue", FabricUIManagerBinding::drainPreallocateViewsQueue), @@ -816,7 +829,17 @@ void FabricUIManagerBinding::registerNatives() { makeNativeMethod( "getRelativeAncestorList", FabricUIManagerBinding::getRelativeAncestorList), + makeNativeMethod( + "setAnimationBackendChoreographer", + FabricUIManagerBinding::setAnimationBackendChoreographer), }); } +void FabricUIManagerBinding::setAnimationBackendChoreographer( + jni::alias_ref + animationBackendChoreographer) { + animationChoreographer_ = std::make_shared( + animationBackendChoreographer); +} + } // namespace facebook::react 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..a84659c54123d8 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 @@ -21,6 +21,7 @@ #include #include +#include "AndroidAnimationChoreographer.h" #include "JFabricUIManager.h" #include "SurfaceHandlerBinding.h" @@ -116,6 +117,8 @@ class FabricUIManagerBinding : public jni::HybridClass, void driveCxxAnimations(); + void driveAnimationBackend(jdouble frameTimeMs); + void drainPreallocateViewsQueue(); void reportMount(SurfaceId surfaceId); @@ -153,6 +156,10 @@ class FabricUIManagerBinding : public jni::HybridClass, float pointScaleFactor_ = 1; bool enableFabricLogs_{false}; + + std::shared_ptr animationChoreographer_; + + void setAnimationBackendChoreographer(jni::alias_ref animationBackend); }; } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.cpp new file mode 100644 index 00000000000000..1f0272d11ae42f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.cpp @@ -0,0 +1,23 @@ +/* + * 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 "JAnimationBackendChoreographer.h" + +namespace facebook::react { + +void JAnimationBackendChoreographer::resume() const { + static const auto resumeMethod = + javaClassStatic()->getMethod("resume"); + resumeMethod(self()); +} + +void JAnimationBackendChoreographer::pause() const { + static const auto pauseMethod = javaClassStatic()->getMethod("pause"); + pauseMethod(self()); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.h new file mode 100644 index 00000000000000..064dac173aca5a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/JAnimationBackendChoreographer.h @@ -0,0 +1,37 @@ +/* + * 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 { + +/** + * JNI wrapper for the AnimationBackendChoreographer Kotlin class. + * This class provides the bridge between C++ and the Android AnimationBackendChoreographer + * which handles animation frame scheduling via ReactChoreographer. + */ +class JAnimationBackendChoreographer : public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/fabric/AnimationBackendChoreographer;"; + + /** + * Resumes animation frame callbacks. + * This method should be called when animations need to start or resume. + */ + void resume() const; + + /** + * Pauses animation frame callbacks. + * This method should be called when animations should be paused (e.g., when + * the app goes to background). + */ + void pause() const; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 3e31bdd04e05d8..7b452f6e4cf7fc 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..1de48a496266f4 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -35,10 +35,6 @@ #include #include -#ifdef RN_USE_ANIMATION_BACKEND -#include -#endif - namespace facebook::react { // Global function pointer for getting current time. Current time @@ -559,10 +555,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); + animationBackendCallbackId_ = animationBackend->start( + [this](float /*f*/) { return pullAnimationMutations(); }); } #endif @@ -583,11 +577,13 @@ void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded( auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(false); if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { +#ifdef RN_USE_ANIMATION_BACKEND if (isRenderCallbackStarted) { if (auto animationBackend = animationBackend_.lock()) { - animationBackend->stop(isAsync); + animationBackend->stop(animationBackendCallbackId_); } } +#endif return; } diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h index 95267a259b7f05..523774037a03f4 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h @@ -286,6 +286,10 @@ class NativeAnimatedNodesManager { bool warnedAboutGraphTraversal_ = false; #endif +#ifdef RN_USE_ANIMATION_BACKEND + CallbackId animationBackendCallbackId_{0}; +#endif + friend class ColorAnimatedNode; friend class AnimationDriver; friend class AnimationTestsBase; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index b0d08ce645fe6c..bd451c45d0cbef 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -88,24 +88,13 @@ 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_); - - nativeAnimatedDelegate_ = - std::make_shared( - animationBackend_); - - uiManager->unstable_setAnimationBackend(animationBackend_); + std::make_shared(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..9b1164d3278fa2 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.h @@ -32,7 +32,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 0b0d25e9d3739e..79fb12dfd6500a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -6,26 +6,16 @@ */ #include "AnimationBackend.h" +#include "AnimatedPropsRegistry.h" + #include #include #include #include -#include "AnimatedPropsRegistry.h" +#include namespace facebook::react { -UIManagerNativeAnimatedDelegateBackendImpl:: - UIManagerNativeAnimatedDelegateBackendImpl( - std::weak_ptr animationBackend) - : animationBackend_(std::move(animationBackend)) {} - -void UIManagerNativeAnimatedDelegateBackendImpl::runAnimationFrame() { - if (auto animationBackendStrong = animationBackend_.lock()) { - animationBackendStrong->onAnimationFrame( - std::chrono::steady_clock::now().time_since_epoch().count() / 1000); - } -} - static inline Props::Shared cloneProps( AnimatedProps& animatedProps, const ShadowNode& shadowNode) { @@ -51,27 +41,27 @@ 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)), - animatedPropsRegistry_(std::make_shared()), - uiManager_(uiManager), - jsInvoker_(std::move(jsInvoker)), - commitHook_(uiManager, animatedPropsRegistry_) {} + std::shared_ptr animationChoreographer, + std::shared_ptr uiManager) + : animatedPropsRegistry_(std::make_shared()), + animationChoreographer_(std::move(animationChoreographer)), + commitHook_(*uiManager, animatedPropsRegistry_), + uiManager_(std::move(uiManager)) { + react_native_assert(uiManager_.expired() == false); +} void AnimationBackend::onAnimationFrame(double timestamp) { + std::vector callbacksCopy; std::unordered_map surfaceUpdates; std::set asyncFlushSurfaces; - for (auto& callback : callbacks) { - auto mutations = callback(static_cast(timestamp)); + { + std::lock_guard lock(mutex_); + callbacksCopy = callbacks; + } + + for (auto& callbackWithId : callbacksCopy) { + auto mutations = callbackWithId.callback(static_cast(timestamp)); asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces); for (auto& mutation : mutations.batch) { const auto family = mutation.family; @@ -98,25 +88,34 @@ void AnimationBackend::onAnimationFrame(double timestamp) { requestAsyncFlushForSurfaces(asyncFlushSurfaces); } -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); +CallbackId AnimationBackend::start(const Callback& callback) { + std::lock_guard lock(mutex_); + + auto callbackId = nextCallbackId_++; + callbacks.push_back({.callbackId = callbackId, .callback = callback}); + if (!isRenderCallbackStarted_) { + animationChoreographer_->resume(); + isRenderCallbackStarted_ = true; } + + return callbackId; } -void AnimationBackend::stop(bool isAsync) { - if (stopOnRenderCallback_) { - stopOnRenderCallback_(isAsync); + +void AnimationBackend::stop(CallbackId callbackId) { + std::lock_guard lock(mutex_); + + auto it = std::find_if(callbacks.begin(), callbacks.end(), [&](auto& c) { + return c.callbackId == callbackId; + }); + if (it == callbacks.end()) { + return; + } + + callbacks.erase(it); + if (isRenderCallbackStarted_ && callbacks.empty()) { + animationChoreographer_->pause(); + isRenderCallbackStarted_ = false; } - callbacks.clear(); } void AnimationBackend::trigger() { @@ -127,9 +126,15 @@ void AnimationBackend::trigger() { void AnimationBackend::commitUpdates( SurfaceId surfaceId, SurfaceUpdates& surfaceUpdates) { + auto uiManager = uiManager_.lock(); + if (!uiManager) { + return; + } + auto& surfaceFamilies = surfaceUpdates.families; auto& updates = surfaceUpdates.propsMap; - uiManager_->getShadowTreeRegistry().visit( + + uiManager->getShadowTreeRegistry().visit( surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) { shadowTree.commit( [&surfaceFamilies, @@ -160,19 +165,28 @@ void AnimationBackend::synchronouslyUpdateProps( const std::unordered_map& updates) { for (auto& [tag, animatedProps] : updates) { // TODO: We shouldn't repack it into dynamic, but for that a rewrite - // of directManipulationCallback_ is needed + // of synchronouslyUpdateViewOnUIThread is needed auto dyn = animationbackend::packAnimatedProps(animatedProps); - directManipulationCallback_(tag, std::move(dyn)); + if (auto uiManager = uiManager_.lock()) { + uiManager->synchronouslyUpdateViewOnUIThread(tag, dyn); + } } } 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 - jsInvoker_->invokeAsync([this, surfaceId]() { - uiManager_->getShadowTreeRegistry().visit( + jsInvoker_->invokeAsync([weakUIManager = uiManager_, surfaceId]() { + auto uiManager = weakUIManager.lock(); + if (!uiManager) { + return; + } + uiManager->getShadowTreeRegistry().visit( surfaceId, [](const ShadowTree& shadowTree) { shadowTree.commit( [](const RootShadowNode& oldRootShadowNode) { @@ -189,4 +203,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 62030edff94f67..cb80194346a573 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -19,21 +19,12 @@ #include "AnimatedProps.h" #include "AnimatedPropsRegistry.h" #include "AnimationBackendCommitHook.h" +#include "AnimationChoreographer.h" namespace facebook::react { class AnimationBackend; -class UIManagerNativeAnimatedDelegateBackendImpl : public UIManagerNativeAnimatedDelegate { - public: - explicit UIManagerNativeAnimatedDelegateBackendImpl(std::weak_ptr animationBackend); - - void runAnimationFrame() override; - - private: - std::weak_ptr animationBackend_; -}; - struct AnimationMutation { Tag tag; std::shared_ptr family; @@ -46,39 +37,41 @@ struct AnimationMutations { std::set asyncFlushSurfaces; }; +using Callback = std::function; + +struct CallbackWithId { + CallbackId callbackId; + Callback callback; +}; + class AnimationBackend : public UIManagerAnimationBackend { public: - using Callback = std::function; - using StartOnRenderCallback = std::function &&, bool /* isAsync */)>; - using StopOnRenderCallback = 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 jsInvoker_; - AnimationBackendCommitHook commitHook_; + using ResumeCallback = std::function; + using PauseCallback = std::function; AnimationBackend( - StartOnRenderCallback &&startOnRenderCallback, - StopOnRenderCallback &&stopOnRenderCallback, - DirectManipulationCallback &&directManipulationCallback, - FabricCommitCallback &&fabricCommitCallback, - UIManager *uiManager, - std::shared_ptr jsInvoker); + std::shared_ptr animationChoreographer, + 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 stop(bool isAsync) override; + CallbackId start(const Callback &callback) override; + void stop(CallbackId callbackId) override; + + private: + std::vector callbacks; + std::shared_ptr animatedPropsRegistry_; + std::shared_ptr animationChoreographer_; + AnimationBackendCommitHook commitHook_; + std::weak_ptr uiManager_; + std::shared_ptr jsInvoker_; + bool isRenderCallbackStarted_{false}; + CallbackId nextCallbackId_{0}; + std::mutex mutex_; }; } // 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..19898d233e610c 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp @@ -7,13 +7,15 @@ #include +#include + namespace facebook::react { AnimationBackendCommitHook::AnimationBackendCommitHook( - UIManager* uiManager, + UIManager& uiManager, std::shared_ptr animatedPropsRegistry) : animatedPropsRegistry_(std::move(animatedPropsRegistry)) { - uiManager->registerCommitHook(*this); + uiManager.registerCommitHook(*this); } RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit( diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h index 7d6c723d6632c9..83b9bcf7899277 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h @@ -19,7 +19,7 @@ class AnimationBackendCommitHook : public UIManagerCommitHook { std::shared_ptr animatedPropsRegistry_; public: - AnimationBackendCommitHook(UIManager *uiManager, std::shared_ptr animatedPropsRegistry); + AnimationBackendCommitHook(UIManager &uiManager, std::shared_ptr animatedPropsRegistry); RootShadowNode::Unshared shadowTreeWillCommit( const ShadowTree &shadowTree, const RootShadowNode::Shared &oldRootShadowNode, diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationChoreographer.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationChoreographer.h new file mode 100644 index 00000000000000..43179eba6446a7 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationChoreographer.h @@ -0,0 +1,39 @@ +/* + * 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 setAnimationBackend(std::weak_ptr animationBackend) + { + animationBackend_ = animationBackend; + } + void onAnimationFrame(float timestamp) const + { + if (auto animationBackend = animationBackend_.lock()) { + animationBackend->onAnimationFrame(timestamp); + } + } + + private: + std::weak_ptr animationBackend_; +}; + +} // 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..268cffdc191f10 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -21,6 +21,9 @@ #include #include #include +#ifdef RN_USE_ANIMATION_BACKEND +#include +#endif namespace facebook::react { @@ -55,6 +58,18 @@ Scheduler::Scheduler( auto uiManager = std::make_shared(runtimeExecutor_, contextContainer_); + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { +#ifdef RN_USE_ANIMATION_BACKEND + auto animationBackend = std::make_shared( + schedulerToolbox.animationChoreographer, uiManager); + + schedulerToolbox.animationChoreographer->setAnimationBackend( + animationBackend); + + uiManager->unstable_setAnimationBackend(animationBackend); +#endif + } + auto eventOwnerBox = std::make_shared(); eventOwnerBox->owner = eventDispatcher_; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index c324655da7255a..80be380a48e1a7 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -46,8 +46,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; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerToolbox.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerToolbox.h index f1446cf4c0dc1f..d38e85303edee4 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerToolbox.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerToolbox.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,12 @@ struct SchedulerToolbox final { * A list of `UIManagerCommitHook`s that should be registered in `UIManager`. */ std::vector> commitHooks; + + /* + * Platform-specific choreographer for scheduling animation frame + * callbacks. Required when useSharedAnimatedBackend() is enabled. + */ + std::shared_ptr animationChoreographer; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 931bfce1c1e10a..c1d12096c2fda5 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -207,9 +207,7 @@ void UIManager::completeSurface( surfaceId, shadowTree.getCurrentRevision().rootShadowNode); if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - if (auto animationBackend = animationBackend_.lock()) { - animationBackend->clearRegistry(surfaceId); - } + animationBackend_->clearRegistry(surfaceId); } } }); @@ -437,8 +435,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 +527,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,7 +685,7 @@ void UIManager::setNativeAnimatedDelegate( } void UIManager::unstable_setAnimationBackend( - std::weak_ptr animationBackend) { + std::shared_ptr animationBackend) { animationBackend_ = animationBackend; } @@ -704,8 +702,10 @@ void UIManager::animationTick() const { }); } - if (auto nativeAnimatedDelegate = nativeAnimatedDelegate_.lock()) { - nativeAnimatedDelegate->runAnimationFrame(); + if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + if (auto nativeAnimatedDelegate = nativeAnimatedDelegate_.lock()) { + nativeAnimatedDelegate->runAnimationFrame(); + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 794a5559a553fe..eac4b5d9a49a1a 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(); /** @@ -248,7 +248,7 @@ class UIManager final : public ShadowTreeDelegate { std::unique_ptr lazyShadowTreeRevisionConsistencyManager_; - std::weak_ptr animationBackend_; + std::shared_ptr animationBackend_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h index 75b6232810457c..f7de19c4a80233 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 namespace facebook::react { +struct AnimationMutations; +using CallbackId = uint64_t; + 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 stop(bool isAsync) = 0; + virtual CallbackId start(const Callback &callback) = 0; + virtual void stop(CallbackId callbackId) = 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/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index ca1f38300f3824..7affd88dca0427 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()) { @@ -223,11 +227,13 @@ void ReactHost::createReactInstance() { return runLoopObserverManager->createEventBeat( ownerBox, *runtimeScheduler); }; + toolbox.animationChoreographer = reactInstanceData_->animationChoreographer; schedulerDelegate_ = std::make_unique( reactInstanceData_->mountingManager); scheduler_ = std::make_unique(toolbox, nullptr, schedulerDelegate_.get()); + 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..47645e4a119842 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..0c9f9a5d07ab13 --- /dev/null +++ b/private/react-native-fantom/tester/src/TesterAnimationChoreographer.cpp @@ -0,0 +1,27 @@ +/* + * 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 + +namespace facebook::react { + +void TesterAnimationChoreographer::resume() { + isPaused_ = false; +} +void TesterAnimationChoreographer::pause() { + isPaused_ = true; +} + +void TesterAnimationChoreographer::runUITick(float timestamp) { + if (!isPaused_) { + onAnimationFrame(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..d60c8e8a0b56f3 --- /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..35afbb35aabd3e 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(static_cast(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();