diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index f903efa0fd4..e19c74b1138 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -54,12 +54,23 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) add_rncore_dependency(s) + + s.subspec "animated" do |ss| + ss.source_files = podspec_sources("react/renderer/animated/**/*.{m,mm,cpp,h}", "react/renderer/animated/**/*.{h}") + ss.exclude_files = "react/renderer/animated/tests" + ss.header_dir = "react/renderer/animated" + end s.subspec "animations" do |ss| ss.source_files = podspec_sources("react/renderer/animations/**/*.{m,mm,cpp,h}", "react/renderer/animations/**/*.{h}") ss.exclude_files = "react/renderer/animations/tests" ss.header_dir = "react/renderer/animations" end + + s.subspec "animationbackend" do |ss| + ss.source_files = podspec_sources("react/renderer/animationbackend/**/*.{m,mm,cpp,h}", "react/renderer/animationbackend/**/*.{h}") + ss.header_dir = "react/renderer/animationbackend" + end s.subspec "attributedstring" do |ss| ss.source_files = podspec_sources("react/renderer/attributedstring/**/*.{m,mm,cpp,h}", "react/renderer/attributedstring/**/*.{h}") diff --git a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h index f7d72415d50..a0f84ccbb2c 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h @@ -7,7 +7,11 @@ #pragma once +#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple +#include "FBReactNativeSpecJSI.h" +#else #include +#endif #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 9008d3eb992..24fe747652f 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -852,7 +852,11 @@ void NativeAnimatedNodesManager::schedulePropsCommit( bool layoutStyleUpdated, bool forceFabricCommit) noexcept { if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - mergeObjects(updateViewProps_[viewTag], props); + if (layoutStyleUpdated) { + mergeObjects(updateViewProps_[viewTag], props); + } else { + mergeObjects(updateViewPropsDirect_[viewTag], props); + } return; } @@ -910,6 +914,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { auto timestamp = static_cast(microseconds) / 1000.0; bool containsChange = false; + AnimatedPropsBuilder propsBuilder; { // copied from onAnimationFrame // Run all active animations @@ -949,9 +954,13 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { } } - for (auto& [tag, props] : updateViewProps_) { - mutations.emplace_back( - AnimationMutation{tag, props["opacity"].asDouble()}); + for (auto& [tag, props] : updateViewPropsDirect_) { + // TODO: also handle layout props (updateViewProps_). It is skipped for + // now, because the backend requires shadowNodeFamilies to be able to + // commit to the ShadowTree + propsBuilder.storeDynamic(props); + mutations.push_back( + AnimationMutation{tag, nullptr, propsBuilder.get()}); containsChange = true; } } @@ -978,9 +987,11 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { isEventAnimationInProgress_ = false; - for (auto& [tag, props] : updateViewProps_) { - mutations.emplace_back( - AnimationMutation{tag, props["opacity"].asDouble()}); + for (auto& [tag, props] : updateViewPropsDirect_) { + // TODO: handle layout props + propsBuilder.storeDynamic(props); + mutations.push_back( + AnimationMutation{tag, nullptr, propsBuilder.get()}); } } } else { diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h index 3004a071eb1..c89ce92c9e2 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h @@ -7,7 +7,11 @@ #pragma once +#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple +#include "FBReactNativeSpecJSI.h" +#else #include +#endif #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index 07bb762bc3b..92d25983109 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -71,7 +71,9 @@ NativeAnimatedNodesManagerProvider::getOrCreate( animationBackend_ = std::make_shared( std::move(startOnRenderCallback_), std::move(stopOnRenderCallback_), - std::move(directManipulationCallback)); + std::move(directManipulationCallback), + std::move(fabricCommitCallback), + uiManager); nativeAnimatedNodesManager_ = std::make_shared(animationBackend_); diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedProps.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedProps.h new file mode 100644 index 00000000000..76d6eaaeeb0 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedProps.h @@ -0,0 +1,40 @@ +/* + * 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 + +namespace facebook::react { + +enum PropName { OPACITY, WIDTH, HEIGHT, BORDER_RADII, FLEX, TRANSFORM }; + +struct AnimatedPropBase { + PropName propName; + explicit AnimatedPropBase(PropName propName) : propName(propName) {} + virtual ~AnimatedPropBase() = default; +}; + +template +struct AnimatedProp : AnimatedPropBase { + T value; + AnimatedProp() = default; + AnimatedProp(PropName propName, const T& value) + : AnimatedPropBase{propName}, value(std::move(value)) {} +}; + +template +T get(const std::unique_ptr& animatedProp) { + return static_cast*>(animatedProp.get())->value; +} + +struct AnimatedProps { + std::vector> props; + std::unique_ptr rawProps; +}; +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsBuilder.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsBuilder.h new file mode 100644 index 00000000000..e7a4d4ef11f --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsBuilder.h @@ -0,0 +1,48 @@ +/* + * 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 "AnimatedProps.h" + +namespace facebook::react { + +struct AnimatedPropsBuilder { + std::vector> props; + std::unique_ptr rawProps; + + void setOpacity(Float value) { + props.push_back(std::make_unique>(OPACITY, value)); + } + void setWidth(yoga::Style::SizeLength value) { + props.push_back( + std::make_unique>(WIDTH, value)); + } + void setHeight(yoga::Style::SizeLength value) { + props.push_back( + std::make_unique>(HEIGHT, value)); + } + void setBorderRadii(CascadedBorderRadii& value) { + props.push_back(std::make_unique>( + BORDER_RADII, value)); + } + void setTransform(Transform& t) { + props.push_back( + std::make_unique>(TRANSFORM, std::move(t))); + } + void storeDynamic(folly::dynamic& d) { + rawProps = std::make_unique(std::move(d)); + } + void storeJSI(jsi::Runtime& runtime, jsi::Value& value) { + rawProps = std::make_unique(runtime, value); + } + AnimatedProps get() { + return AnimatedProps{std::move(props), std::move(rawProps)}; + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index 4106a1729e2..a38dd0faf99 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -6,34 +6,169 @@ */ #include "AnimationBackend.h" +#include namespace facebook::react { +static inline Props::Shared cloneProps( + AnimatedProps& animatedProps, + const ShadowNode& shadowNode) { + PropsParserContext propsParserContext{ + shadowNode.getSurfaceId(), *shadowNode.getContextContainer()}; + Props::Shared newProps; + if (animatedProps.rawProps) { + newProps = shadowNode.getComponentDescriptor().cloneProps( + propsParserContext, + shadowNode.getProps(), + std::move(*animatedProps.rawProps)); + } else { + newProps = shadowNode.getComponentDescriptor().cloneProps( + propsParserContext, shadowNode.getProps(), {}); + } + + auto viewProps = std::const_pointer_cast( + std::static_pointer_cast(newProps)); + for (auto& animatedProp : animatedProps.props) { + switch (animatedProp->propName) { + case OPACITY: + viewProps->opacity = get(animatedProp); + break; + + case WIDTH: + viewProps->yogaStyle.setDimension( + yoga::Dimension::Width, get(animatedProp)); + break; + + case HEIGHT: + viewProps->yogaStyle.setDimension( + yoga::Dimension::Height, + get(animatedProp)); + break; + + case BORDER_RADII: + viewProps->borderRadii = get(animatedProp); + break; + + case FLEX: + viewProps->yogaStyle.setFlex(get(animatedProp)); + break; + + case TRANSFORM: + viewProps->transform = get(animatedProp); + break; + } + } + return newProps; +} + +static inline bool mutationHasLayoutUpdates( + facebook::react::AnimationMutation& mutation) { + for (auto& animatedProp : mutation.props.props) { + // TODO: there should also be a check for the dynamic part + if (animatedProp->propName == WIDTH || animatedProp->propName == HEIGHT || + animatedProp->propName == FLEX) { + return true; + } + } + return false; +} + AnimationBackend::AnimationBackend( StartOnRenderCallback&& startOnRenderCallback, StopOnRenderCallback&& stopOnRenderCallback, - DirectManipulationCallback&& directManipulationCallback) + DirectManipulationCallback&& directManipulationCallback, + FabricCommitCallback&& fabricCommitCallback, + UIManager* uiManager) : startOnRenderCallback_(startOnRenderCallback), stopOnRenderCallback_(stopOnRenderCallback), - directManipulationCallback_(directManipulationCallback) {} + directManipulationCallback_(directManipulationCallback), + fabricCommitCallback_(fabricCommitCallback), + uiManager_(uiManager) {} void AnimationBackend::onAnimationFrame(double timestamp) { + std::unordered_map updates; + std::unordered_set families; + bool hasAnyLayoutUpdates = false; for (auto& callback : callbacks) { auto muatations = callback(timestamp); for (auto& mutation : muatations) { - directManipulationCallback_( - mutation.tag, folly::dynamic::object("opacity", mutation.opacity)); + hasAnyLayoutUpdates |= mutationHasLayoutUpdates(mutation); + families.insert(mutation.family); + updates[mutation.tag] = std::move(mutation.props); } } + + if (hasAnyLayoutUpdates) { + commitUpdatesWithFamilies(families, updates); + } else { + synchronouslyUpdateProps(updates); + } } void AnimationBackend::start(const Callback& callback) { callbacks.push_back(callback); - // startOnRenderCallback_ should provide the timestamp from the platform - startOnRenderCallback_([this]() { onAnimationFrame(0); }); + // TODO: startOnRenderCallback_ should provide the timestamp from the platform + startOnRenderCallback_([this]() { + onAnimationFrame( + std::chrono::steady_clock::now().time_since_epoch().count() / 1000); + }); } void AnimationBackend::stop() { stopOnRenderCallback_(); callbacks.clear(); } + +void AnimationBackend::commitUpdatesWithFamilies( + const std::unordered_set& families, + std::unordered_map& updates) { + uiManager_->getShadowTreeRegistry().enumerate( + [families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) { + shadowTree.commit( + [families, &updates](const RootShadowNode& oldRootShadowNode) { + return std::static_pointer_cast( + oldRootShadowNode.cloneMultiple( + families, + [families, &updates]( + const ShadowNode& shadowNode, + const ShadowNodeFragment& fragment) { + auto& animatedProps = updates.at(shadowNode.getTag()); + auto newProps = cloneProps(animatedProps, shadowNode); + return shadowNode.clone( + {newProps, + fragment.children, + shadowNode.getState()}); + })); + }, + {.mountSynchronously = true}); + }); +} + +void AnimationBackend::synchronouslyUpdateProps( + const std::unordered_map& updates) { + for (auto& [tag, animatedProps] : updates) { + auto dyn = animatedProps.rawProps ? animatedProps.rawProps->toDynamic() + : folly::dynamic::object(); + for (auto& animatedProp : animatedProps.props) { + // TODO: We shouldn't repack it into dynamic, but for that a rewrite of + // directManipulationCallback_ is needed + switch (animatedProp->propName) { + case OPACITY: + dyn.insert("opacity", get(animatedProp)); + break; + + case BORDER_RADII: + case TRANSFORM: + // TODO: handle other things than opacity + break; + + case WIDTH: + case HEIGHT: + case FLEX: + throw "Tried to synchronously update layout props"; + } + } + directManipulationCallback_(tag, dyn); + } +} + } // 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 61b11e459d0..e8991c5ad6a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -9,14 +9,18 @@ #include #include +#include #include #include +#include "AnimatedProps.h" +#include "AnimatedPropsBuilder.h" namespace facebook::react { struct AnimationMutation { Tag tag; - double opacity; + const ShadowNodeFamily* family; + AnimatedProps props; }; using AnimationMutations = std::vector; @@ -25,6 +29,8 @@ using StartOnRenderCallback = std::function&&)>; using StopOnRenderCallback = std::function; using DirectManipulationCallback = std::function; +using FabricCommitCallback = + std::function&)>; class AnimationBackend { public: @@ -32,11 +38,21 @@ class AnimationBackend { const StartOnRenderCallback startOnRenderCallback_; const StopOnRenderCallback stopOnRenderCallback_; const DirectManipulationCallback directManipulationCallback_; + const FabricCommitCallback fabricCommitCallback_; + UIManager* uiManager_; AnimationBackend( StartOnRenderCallback&& startOnRenderCallback, StopOnRenderCallback&& stopOnRenderCallback, - DirectManipulationCallback&& directManipulationCallback); + DirectManipulationCallback&& directManipulationCallback, + FabricCommitCallback&& fabricCommitCallback, + UIManager* uiManager); + void commitUpdatesWithFamilies( + const std::unordered_set& families, + std::unordered_map& updates); + void synchronouslyUpdateProps( + const std::unordered_map& updates); + void onAnimationFrame(double timestamp); void start(const Callback& callback); void stop();