/* * 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 #if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple #include "FBReactNativeSpecJSI.h" #else #include #endif #include #include #include #include #include #ifdef RN_USE_ANIMATION_BACKEND #include #endif #include #include #include #include #include #include #include #include #include namespace facebook::react { using TimePointFunction = std::chrono::steady_clock::time_point (*)(); // A way to inject a custom time function for testing purposes. // Default is `std::chrono::steady_clock::now`. void g_setNativeAnimatedNowTimestampFunction(TimePointFunction nowFunction); class AnimatedNode; class AnimationDriver; class Scheduler; using ValueListenerCallback = std::function; using UiTask = std::function; using EndResult = NativeAnimatedTurboModuleEndResult, std::optional>; using AnimationEndCallback = AsyncCallback; template <> struct Bridging : NativeAnimatedTurboModuleEndResultBridging {}; class NativeAnimatedNodesManager { public: using DirectManipulationCallback = std::function; using FabricCommitCallback = std::function &)>; using StartOnRenderCallback = std::function &&, bool isAsync)>; using StopOnRenderCallback = std::function; using FrameRateListenerCallback = std::function; explicit NativeAnimatedNodesManager( DirectManipulationCallback &&directManipulationCallback, FabricCommitCallback &&fabricCommitCallback, StartOnRenderCallback &&startOnRenderCallback = nullptr, StopOnRenderCallback &&stopOnRenderCallback = nullptr, FrameRateListenerCallback &&frameRateListenerCallback = nullptr) noexcept; explicit NativeAnimatedNodesManager(std::shared_ptr animationBackend) noexcept; ~NativeAnimatedNodesManager() noexcept; template >> T *getAnimatedNode(Tag tag) const requires(std::is_base_of_v) { if (auto it = animatedNodes_.find(tag); it != animatedNodes_.end()) { return static_cast(it->second.get()); } return nullptr; } std::optional getValue(Tag tag) noexcept; #pragma mark - Graph // Called from JS thread void createAnimatedNodeAsync(Tag tag, const folly::dynamic &config) noexcept; void createAnimatedNode(Tag tag, const folly::dynamic &config) noexcept; void connectAnimatedNodes(Tag parentTag, Tag childTag) noexcept; void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept; void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept; void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept; void restoreDefaultValues(Tag tag) noexcept; void dropAnimatedNode(Tag tag) noexcept; void setAnimatedNodeValue(Tag tag, double value); void flattenAnimatedNodeOffset(Tag tag); void extractAnimatedNodeOffsetOp(Tag tag); void setAnimatedNodeOffset(Tag tag, double offset); #ifdef RN_USE_ANIMATION_BACKEND AnimationMutations pullAnimationMutations(); #endif #pragma mark - Drivers void startAnimatingNode( int animationId, Tag animatedNodeTag, folly::dynamic config, std::optional endCallback) noexcept; void stopAnimation(int animationId, bool isTrackingAnimation = false) noexcept; void addAnimatedEventToView(Tag viewTag, const std::string &eventName, const folly::dynamic &eventMapping) noexcept; void removeAnimatedEventFromView(Tag viewTag, const std::string &eventName, Tag animatedValueTag) noexcept; std::shared_ptr getEventEmitterListener() noexcept { return ensureEventEmitterListener(); } #pragma mark - Listeners void startListeningToAnimatedNodeValue(Tag tag, ValueListenerCallback &&callback) noexcept; void stopListeningToAnimatedNodeValue(Tag tag) noexcept; void schedulePropsCommit( Tag viewTag, const folly::dynamic &props, bool layoutStyleUpdated, bool forceFabricCommit) noexcept; /** * Commits all pending animated property updates to their respective views. * * This method is the final step in the animation pipeline that applies * calculated property values to the actual UI components. It uses * Fabric-based updates if layout properties are affected, otherwise uses * direct manipulation. * * returns boolean indicating whether any changes were committed to views. * Returns true if no changes were made, which helps the animation * system determine if animations are still active. */ bool commitProps(); void scheduleOnUI(UiTask &&task) { { std::lock_guard lock(uiTasksMutex_); operations_.push_back(std::move(task)); } // Whenever a batch is flushed to the UI thread, start the onRender // callbacks to guarantee they run at least once. E.g., to execute // setValue calls. startRenderCallbackIfNeeded(true); } void onRender(); void startRenderCallbackIfNeeded(bool isAsync); void updateNodes(const std::set &finishedAnimationValueNodes = {}) noexcept; folly::dynamic managedProps(Tag tag) const noexcept; bool hasManagedProps() const noexcept; void onManagedPropsRemoved(Tag tag) noexcept; bool isOnRenderThread() const noexcept; private: void stopRenderCallbackIfNeeded(bool isAsync) noexcept; bool onAnimationFrame(double timestamp); bool isAnimationUpdateNeeded() const noexcept; void stopAnimationsForNode(Tag nodeTag); std::shared_ptr ensureEventEmitterListener() noexcept; void handleAnimatedEvent(Tag tag, const std::string &eventName, const EventPayload &payload) noexcept; std::weak_ptr animationBackend_; std::unique_ptr animatedNode(Tag tag, const folly::dynamic &config) noexcept; static thread_local bool isOnRenderThread_; std::mutex animatedNodesCreatedAsyncMutex_; std::unordered_map> animatedNodesCreatedAsync_; std::unordered_map> animatedNodes_; std::unordered_map connectedAnimatedNodes_; std::unordered_map> activeAnimations_; std::unordered_map< EventAnimationDriverKey, std::vector>, std::hash> eventDrivers_; std::unordered_set updatedNodeTags_; mutable std::mutex connectedAnimatedNodesMutex_; std::mutex uiTasksMutex_; std::vector operations_; /* * Tracks whether a event-driven animation is currently in progress. * This is set to true when an event handler triggers an animation, * and reset to false when UI tick results in no changes to UI from * animations. */ bool isEventAnimationInProgress_{false}; // React context required to commit props onto Component View const DirectManipulationCallback directManipulationCallback_; const FabricCommitCallback fabricCommitCallback_; /* * Tracks whether the render callback loop for animations is currently active. */ std::atomic_bool isRenderCallbackStarted_{false}; const StartOnRenderCallback startOnRenderCallback_; const StopOnRenderCallback stopOnRenderCallback_; const FrameRateListenerCallback frameRateListenerCallback_; std::shared_ptr eventEmitterListener_{nullptr}; std::unordered_map updateViewProps_{}; std::unordered_map updateViewPropsDirect_{}; /* * Sometimes a view is not longer connected to a PropsAnimatedNode, but * NativeAnimated has previously changed the view's props via direct * manipulation, we use unsyncedDirectViewProps_ to keep track of those * props, to make sure later Fabric commits will not override direct * manipulation result on this view. */ mutable std::mutex unsyncedDirectViewPropsMutex_; std::unordered_map unsyncedDirectViewProps_{}; int animatedGraphBFSColor_ = 0; #ifdef REACT_NATIVE_DEBUG bool warnedAboutGraphTraversal_ = false; #endif friend class ColorAnimatedNode; friend class AnimationDriver; friend class AnimationTestsBase; }; } // namespace facebook::react