mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Animation Backend init (#53526)
Summary: This diff adds AnimatedProps class that allows prop diffs to be passed to the backend. Currently it is implemented through subclassing, but the implementation details are subject to change. To simplify integration with animation frameworks AnimatedPropsBuilder class was created - frameworks can use it to prepare their diffs. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [GENERAL] [ADDED] - AnimatedProps.h AnimatedPropsBuilder.h [GENERAL] [CHANGED] - AnimationBackend initialisation and style updates For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: https://github.com/facebook/react-native/pull/53526 Test Plan: js1 fantom AnimatedBackend-itest.js js1 fantom Animated-itest.js Differential Revision: D81309558 Pulled By: bartlomiejbloniarz
This commit is contained in:
committed by
Facebook GitHub Bot
parent
53a872fb4f
commit
635f89aaa7
@@ -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}")
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
|
||||
#include "FBReactNativeSpecJSI.h"
|
||||
#else
|
||||
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
#endif
|
||||
#include <ReactCommon/TurboModuleWithJSIBindings.h>
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
|
||||
|
||||
+18
-7
@@ -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<double>(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 {
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
|
||||
#include "FBReactNativeSpecJSI.h"
|
||||
#else
|
||||
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
#endif
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/bridging/Function.h>
|
||||
#include <react/debug/flags.h>
|
||||
|
||||
+3
-1
@@ -71,7 +71,9 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
|
||||
animationBackend_ = std::make_shared<AnimationBackend>(
|
||||
std::move(startOnRenderCallback_),
|
||||
std::move(stopOnRenderCallback_),
|
||||
std::move(directManipulationCallback));
|
||||
std::move(directManipulationCallback),
|
||||
std::move(fabricCommitCallback),
|
||||
uiManager);
|
||||
|
||||
nativeAnimatedNodesManager_ =
|
||||
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
|
||||
|
||||
@@ -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 <react/renderer/components/view/BaseViewProps.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
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 <typename T>
|
||||
struct AnimatedProp : AnimatedPropBase {
|
||||
T value;
|
||||
AnimatedProp() = default;
|
||||
AnimatedProp(PropName propName, const T& value)
|
||||
: AnimatedPropBase{propName}, value(std::move(value)) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T get(const std::unique_ptr<AnimatedPropBase>& animatedProp) {
|
||||
return static_cast<AnimatedProp<T>*>(animatedProp.get())->value;
|
||||
}
|
||||
|
||||
struct AnimatedProps {
|
||||
std::vector<std::unique_ptr<AnimatedPropBase>> props;
|
||||
std::unique_ptr<RawProps> rawProps;
|
||||
};
|
||||
} // namespace facebook::react
|
||||
+48
@@ -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 <react/renderer/components/view/BaseViewProps.h>
|
||||
#include "AnimatedProps.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
struct AnimatedPropsBuilder {
|
||||
std::vector<std::unique_ptr<AnimatedPropBase>> props;
|
||||
std::unique_ptr<RawProps> rawProps;
|
||||
|
||||
void setOpacity(Float value) {
|
||||
props.push_back(std::make_unique<AnimatedProp<Float>>(OPACITY, value));
|
||||
}
|
||||
void setWidth(yoga::Style::SizeLength value) {
|
||||
props.push_back(
|
||||
std::make_unique<AnimatedProp<yoga::Style::SizeLength>>(WIDTH, value));
|
||||
}
|
||||
void setHeight(yoga::Style::SizeLength value) {
|
||||
props.push_back(
|
||||
std::make_unique<AnimatedProp<yoga::Style::SizeLength>>(HEIGHT, value));
|
||||
}
|
||||
void setBorderRadii(CascadedBorderRadii& value) {
|
||||
props.push_back(std::make_unique<AnimatedProp<CascadedBorderRadii>>(
|
||||
BORDER_RADII, value));
|
||||
}
|
||||
void setTransform(Transform& t) {
|
||||
props.push_back(
|
||||
std::make_unique<AnimatedProp<Transform>>(TRANSFORM, std::move(t)));
|
||||
}
|
||||
void storeDynamic(folly::dynamic& d) {
|
||||
rawProps = std::make_unique<RawProps>(std::move(d));
|
||||
}
|
||||
void storeJSI(jsi::Runtime& runtime, jsi::Value& value) {
|
||||
rawProps = std::make_unique<RawProps>(runtime, value);
|
||||
}
|
||||
AnimatedProps get() {
|
||||
return AnimatedProps{std::move(props), std::move(rawProps)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+141
-6
@@ -6,34 +6,169 @@
|
||||
*/
|
||||
|
||||
#include "AnimationBackend.h"
|
||||
#include <chrono>
|
||||
|
||||
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<BaseViewProps>(
|
||||
std::static_pointer_cast<const BaseViewProps>(newProps));
|
||||
for (auto& animatedProp : animatedProps.props) {
|
||||
switch (animatedProp->propName) {
|
||||
case OPACITY:
|
||||
viewProps->opacity = get<Float>(animatedProp);
|
||||
break;
|
||||
|
||||
case WIDTH:
|
||||
viewProps->yogaStyle.setDimension(
|
||||
yoga::Dimension::Width, get<yoga::Style::SizeLength>(animatedProp));
|
||||
break;
|
||||
|
||||
case HEIGHT:
|
||||
viewProps->yogaStyle.setDimension(
|
||||
yoga::Dimension::Height,
|
||||
get<yoga::Style::SizeLength>(animatedProp));
|
||||
break;
|
||||
|
||||
case BORDER_RADII:
|
||||
viewProps->borderRadii = get<CascadedBorderRadii>(animatedProp);
|
||||
break;
|
||||
|
||||
case FLEX:
|
||||
viewProps->yogaStyle.setFlex(get<yoga::FloatOptional>(animatedProp));
|
||||
break;
|
||||
|
||||
case TRANSFORM:
|
||||
viewProps->transform = get<Transform>(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<Tag, AnimatedProps> updates;
|
||||
std::unordered_set<const ShadowNodeFamily*> 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<const ShadowNodeFamily*>& families,
|
||||
std::unordered_map<Tag, AnimatedProps>& updates) {
|
||||
uiManager_->getShadowTreeRegistry().enumerate(
|
||||
[families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) {
|
||||
shadowTree.commit(
|
||||
[families, &updates](const RootShadowNode& oldRootShadowNode) {
|
||||
return std::static_pointer_cast<RootShadowNode>(
|
||||
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<Tag, AnimatedProps>& 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<Float>(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
|
||||
|
||||
+18
-2
@@ -9,14 +9,18 @@
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/renderer/core/ReactPrimitives.h>
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "AnimatedProps.h"
|
||||
#include "AnimatedPropsBuilder.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
struct AnimationMutation {
|
||||
Tag tag;
|
||||
double opacity;
|
||||
const ShadowNodeFamily* family;
|
||||
AnimatedProps props;
|
||||
};
|
||||
|
||||
using AnimationMutations = std::vector<AnimationMutation>;
|
||||
@@ -25,6 +29,8 @@ using StartOnRenderCallback = std::function<void(std::function<void()>&&)>;
|
||||
using StopOnRenderCallback = std::function<void()>;
|
||||
using DirectManipulationCallback =
|
||||
std::function<void(Tag, const folly::dynamic&)>;
|
||||
using FabricCommitCallback =
|
||||
std::function<void(std::unordered_map<Tag, folly::dynamic>&)>;
|
||||
|
||||
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<const ShadowNodeFamily*>& families,
|
||||
std::unordered_map<Tag, AnimatedProps>& updates);
|
||||
void synchronouslyUpdateProps(
|
||||
const std::unordered_map<Tag, AnimatedProps>& updates);
|
||||
|
||||
void onAnimationFrame(double timestamp);
|
||||
void start(const Callback& callback);
|
||||
void stop();
|
||||
|
||||
Reference in New Issue
Block a user