first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
/*
* 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 "AnimatedModule.h"
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
namespace facebook::react {
AnimatedModule::AnimatedModule(
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider)
: NativeAnimatedModuleCxxSpec(jsInvoker),
nodesManagerProvider_(std::move(nodesManagerProvider)) {}
void AnimatedModule::startOperationBatch(jsi::Runtime& /*rt*/) {}
void AnimatedModule::finishOperationBatch(jsi::Runtime& /*rt*/) {
// No mutex needed, operations only added via TurboModule method invocation
std::vector<Operation> preOperations;
std::vector<Operation> operations;
std::swap(preOperations_, preOperations);
std::swap(operations_, operations);
if (nodesManager_) {
// TODO: nodesManager_ must exist at all times. But without this check
// AnimatedProps-itest.js fails.
nodesManager_->scheduleOnUI([this,
preOperations = std::move(preOperations),
operations = std::move(operations)]() {
for (auto& preOperation : preOperations) {
executeOperation(preOperation);
}
for (auto& operation : operations) {
executeOperation(operation);
}
});
}
}
void AnimatedModule::createAnimatedNode(
jsi::Runtime& rt,
Tag tag,
jsi::Object config) {
auto configDynamic = dynamicFromValue(rt, jsi::Value(rt, config));
if (auto it = configDynamic.find("disableBatchingForNativeCreate");
it != configDynamic.items().end() && it->second == true) {
if (nodesManager_) {
nodesManager_->createAnimatedNodeAsync(tag, configDynamic);
}
} else {
operations_.emplace_back(
CreateAnimatedNodeOp{.tag = tag, .config = std::move(configDynamic)});
}
}
void AnimatedModule::updateAnimatedNodeConfig(
jsi::Runtime& rt,
Tag tag,
jsi::Object config) {
// TODO(T196513045): missing implementation. This API is only used by Animated
// when PlatformColor API is used and animation is updated with a new value
// through AnimatedColor.setValue.
}
void AnimatedModule::getValue(
jsi::Runtime& /*rt*/,
Tag tag,
AsyncCallback<double> saveValueCallback) {
operations_.emplace_back(
GetValueOp{.tag = tag, .callback = std::move(saveValueCallback)});
}
void AnimatedModule::startListeningToAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag tag) {
operations_.emplace_back(StartListeningToAnimatedNodeValueOp{tag});
}
void AnimatedModule::stopListeningToAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag tag) {
operations_.emplace_back(StopListeningToAnimatedNodeValueOp{tag});
}
void AnimatedModule::connectAnimatedNodes(
jsi::Runtime& /*rt*/,
Tag parentTag,
Tag childTag) {
operations_.emplace_back(
ConnectAnimatedNodesOp{.parentTag = parentTag, .childTag = childTag});
}
void AnimatedModule::disconnectAnimatedNodes(
jsi::Runtime& /*rt*/,
Tag parentTag,
Tag childTag) {
operations_.emplace_back(
DisconnectAnimatedNodesOp{.parentTag = parentTag, .childTag = childTag});
}
void AnimatedModule::startAnimatingNode(
jsi::Runtime& rt,
int animationId,
Tag nodeTag,
jsi::Object config,
AnimationEndCallback endCallback) {
auto configDynamic = dynamicFromValue(rt, jsi::Value(rt, config));
operations_.emplace_back(
StartAnimatingNodeOp{
.animationId = animationId,
.nodeTag = nodeTag,
.config = std::move(configDynamic),
.endCallback = std::move(endCallback)});
}
void AnimatedModule::stopAnimation(jsi::Runtime& /*rt*/, int animationId) {
operations_.emplace_back(StopAnimationOp{animationId});
}
void AnimatedModule::setAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag nodeTag,
double value) {
operations_.emplace_back(
SetAnimatedNodeValueOp{.nodeTag = nodeTag, .value = value});
}
void AnimatedModule::setAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag,
double offset) {
operations_.emplace_back(
SetAnimatedNodeOffsetOp{.nodeTag = nodeTag, .offset = offset});
}
void AnimatedModule::flattenAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag) {
operations_.emplace_back(FlattenAnimatedNodeOffsetOp({.nodeTag = nodeTag}));
}
void AnimatedModule::extractAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag) {
operations_.emplace_back(ExtractAnimatedNodeOffsetOp({.nodeTag = nodeTag}));
}
void AnimatedModule::connectAnimatedNodeToView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
Tag viewTag) {
operations_.emplace_back(
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}
void AnimatedModule::disconnectAnimatedNodeFromView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
Tag viewTag) {
operations_.emplace_back(
DisconnectAnimatedNodeFromViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}
void AnimatedModule::restoreDefaultValues(jsi::Runtime& /*rt*/, Tag nodeTag) {
preOperations_.emplace_back(RestoreDefaultValuesOp{nodeTag});
}
void AnimatedModule::dropAnimatedNode(jsi::Runtime& /*rt*/, Tag tag) {
operations_.emplace_back(DropAnimatedNodeOp{tag});
}
void AnimatedModule::addAnimatedEventToView(
jsi::Runtime& rt,
Tag viewTag,
std::string eventName,
jsi::Object eventMapping) {
auto eventMappingDynamic = dynamicFromValue(rt, jsi::Value(rt, eventMapping));
operations_.emplace_back(
AddAnimatedEventToViewOp{
.viewTag = viewTag,
.eventName = std::move(eventName),
.eventMapping = std::move(eventMappingDynamic)});
}
void AnimatedModule::removeAnimatedEventFromView(
jsi::Runtime& /*rt*/,
Tag viewTag,
std::string eventName,
Tag animatedNodeTag) {
operations_.emplace_back(
RemoveAnimatedEventFromViewOp{
.viewTag = viewTag,
.eventName = std::move(eventName),
.animatedNodeTag = animatedNodeTag});
}
void AnimatedModule::addListener(
jsi::Runtime& /*rt*/,
const std::string& /*eventName*/) {
// Not needed in C++ Animated. addListener is used to synchronise event
// animations like onScroll with React and Fabric. However C++ Animated
// synchronises with Fabric directly.
}
void AnimatedModule::removeListeners(jsi::Runtime& /*rt*/, int /*count*/) {
// Not needed in C++ Animated. removeListeners is used to synchronise event
// animations like onScroll with React and Fabric. However C++ Animated
// synchronises with Fabric directly.
}
void AnimatedModule::queueAndExecuteBatchedOperations(
jsi::Runtime& /*rt*/,
jsi::Array /*operationsAndArgs*/) {
// TODO(T225953475): missing implementation
}
void AnimatedModule::executeOperation(const Operation& operation) {
std::visit(
[&](const auto& op) {
using T = std::decay_t<decltype(op)>;
if constexpr (std::is_same_v<T, CreateAnimatedNodeOp>) {
nodesManager_->createAnimatedNode(op.tag, op.config);
} else if constexpr (std::is_same_v<T, GetValueOp>) {
auto animValue = nodesManager_->getValue(op.tag);
if (animValue) {
op.callback.call(animValue.value());
}
} else if constexpr (std::is_same_v<
T,
StartListeningToAnimatedNodeValueOp>) {
nodesManager_->startListeningToAnimatedNodeValue(
op.tag, [this, tag = op.tag](double value) {
emitDeviceEvent(
"onAnimatedValueUpdate",
[tag, value](
jsi::Runtime& rt, std::vector<jsi::Value>& args) {
auto arg = jsi::Object(rt);
arg.setProperty(rt, "tag", jsi::Value(tag));
arg.setProperty(rt, "value", jsi::Value(value));
args.emplace_back(rt, arg);
});
});
} else if constexpr (std::is_same_v<
T,
StopListeningToAnimatedNodeValueOp>) {
nodesManager_->stopListeningToAnimatedNodeValue(op.tag);
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodesOp>) {
nodesManager_->connectAnimatedNodes(op.parentTag, op.childTag);
} else if constexpr (std::is_same_v<T, DisconnectAnimatedNodesOp>) {
nodesManager_->disconnectAnimatedNodes(op.parentTag, op.childTag);
} else if constexpr (std::is_same_v<T, StartAnimatingNodeOp>) {
nodesManager_->startAnimatingNode(
op.animationId,
op.nodeTag,
std::move(op.config),
std::move(op.endCallback));
} else if constexpr (std::is_same_v<T, StopAnimationOp>) {
nodesManager_->stopAnimation(op.animationId, false);
} else if constexpr (std::is_same_v<T, SetAnimatedNodeValueOp>) {
nodesManager_->setAnimatedNodeValue(op.nodeTag, op.value);
} else if constexpr (std::is_same_v<T, SetAnimatedNodeOffsetOp>) {
nodesManager_->setAnimatedNodeOffset(op.nodeTag, op.offset);
} else if constexpr (std::is_same_v<T, FlattenAnimatedNodeOffsetOp>) {
nodesManager_->flattenAnimatedNodeOffset(op.nodeTag);
} else if constexpr (std::is_same_v<T, ExtractAnimatedNodeOffsetOp>) {
nodesManager_->extractAnimatedNodeOffsetOp(op.nodeTag);
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodeToViewOp>) {
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<
T,
DisconnectAnimatedNodeFromViewOp>) {
nodesManager_->disconnectAnimatedNodeFromView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<T, RestoreDefaultValuesOp>) {
nodesManager_->restoreDefaultValues(op.nodeTag);
} else if constexpr (std::is_same_v<T, DropAnimatedNodeOp>) {
nodesManager_->dropAnimatedNode(op.tag);
} else if constexpr (std::is_same_v<T, AddAnimatedEventToViewOp>) {
nodesManager_->addAnimatedEventToView(
op.viewTag, op.eventName, op.eventMapping);
} else if constexpr (std::is_same_v<T, RemoveAnimatedEventFromViewOp>) {
nodesManager_->removeAnimatedEventFromView(
op.viewTag, op.eventName, op.animatedNodeTag);
}
},
operation);
}
void AnimatedModule::installJSIBindingsWithRuntime(jsi::Runtime& runtime) {
if (nodesManagerProvider_) {
nodesManager_ = nodesManagerProvider_->getOrCreate(runtime, jsInvoker_);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,205 @@
/*
* 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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <ReactCommon/TurboModuleWithJSIBindings.h>
#include <folly/dynamic.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/NativeAnimatedNodesManagerProvider.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <memory>
#include <string>
#include <variant>
namespace facebook::react {
class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>, public TurboModuleWithJSIBindings {
#pragma mark - Operation structures for each type of animated operation
struct CreateAnimatedNodeOp {
Tag tag{};
folly::dynamic config;
};
struct GetValueOp {
Tag tag{};
AsyncCallback<double> callback;
};
struct StartListeningToAnimatedNodeValueOp {
Tag tag{};
};
struct StopListeningToAnimatedNodeValueOp {
Tag tag{};
};
struct ConnectAnimatedNodesOp {
Tag parentTag{};
Tag childTag{};
};
struct DisconnectAnimatedNodesOp {
Tag parentTag{};
Tag childTag{};
};
struct StartAnimatingNodeOp {
int animationId{};
Tag nodeTag{};
folly::dynamic config;
AnimationEndCallback endCallback;
};
struct StopAnimationOp {
int animationId{};
};
struct SetAnimatedNodeValueOp {
Tag nodeTag{};
double value{};
};
struct SetAnimatedNodeOffsetOp {
Tag nodeTag{};
double offset{};
};
struct FlattenAnimatedNodeOffsetOp {
Tag nodeTag{};
};
struct ExtractAnimatedNodeOffsetOp {
Tag nodeTag{};
};
struct ConnectAnimatedNodeToViewOp {
Tag nodeTag{};
Tag viewTag{};
};
struct DisconnectAnimatedNodeFromViewOp {
Tag nodeTag{};
Tag viewTag{};
};
struct RestoreDefaultValuesOp {
Tag nodeTag{};
};
struct DropAnimatedNodeOp {
Tag tag{};
};
struct AddAnimatedEventToViewOp {
Tag viewTag{};
std::string eventName;
folly::dynamic eventMapping;
};
struct RemoveAnimatedEventFromViewOp {
Tag viewTag{};
std::string eventName;
Tag animatedNodeTag{};
};
using Operation = std::variant<
CreateAnimatedNodeOp,
GetValueOp,
StartListeningToAnimatedNodeValueOp,
StopListeningToAnimatedNodeValueOp,
ConnectAnimatedNodesOp,
DisconnectAnimatedNodesOp,
StartAnimatingNodeOp,
StopAnimationOp,
SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
ExtractAnimatedNodeOffsetOp,
DropAnimatedNodeOp,
AddAnimatedEventToViewOp,
RemoveAnimatedEventFromViewOp>;
#pragma mark -
public:
AnimatedModule(
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider);
void startOperationBatch(jsi::Runtime &rt);
void finishOperationBatch(jsi::Runtime &rt);
void createAnimatedNode(jsi::Runtime &rt, Tag tag, jsi::Object config);
void updateAnimatedNodeConfig(jsi::Runtime &rt, Tag tag, jsi::Object config);
void getValue(jsi::Runtime &rt, Tag tag, AsyncCallback<double> saveValueCallback);
void startListeningToAnimatedNodeValue(jsi::Runtime &rt, Tag tag);
void stopListeningToAnimatedNodeValue(jsi::Runtime &rt, Tag tag);
void connectAnimatedNodes(jsi::Runtime &rt, Tag parentTag, Tag childTag);
void disconnectAnimatedNodes(jsi::Runtime &rt, Tag parentTag, Tag childTag);
void startAnimatingNode(
jsi::Runtime &rt,
int animationId,
Tag nodeTag,
jsi::Object config,
AnimationEndCallback endCallback);
void stopAnimation(jsi::Runtime &rt, int animationId);
void setAnimatedNodeValue(jsi::Runtime &rt, Tag nodeTag, double value);
void setAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag, double offset);
void flattenAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag);
void extractAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag);
void connectAnimatedNodeToView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
void disconnectAnimatedNodeFromView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
void restoreDefaultValues(jsi::Runtime &rt, Tag nodeTag);
void dropAnimatedNode(jsi::Runtime &rt, Tag tag);
void addAnimatedEventToView(jsi::Runtime &rt, Tag viewTag, std::string eventName, jsi::Object eventMapping);
void removeAnimatedEventFromView(jsi::Runtime &rt, Tag viewTag, std::string eventName, Tag animatedNodeTag);
void addListener(jsi::Runtime &rt, const std::string &eventName);
void removeListeners(jsi::Runtime &rt, int count);
void queueAndExecuteBatchedOperations(jsi::Runtime &rt, jsi::Array operationsAndArgs);
private:
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider_;
std::shared_ptr<NativeAnimatedNodesManager> nodesManager_;
std::vector<Operation> preOperations_;
std::vector<Operation> operations_;
void executeOperation(const Operation &operation);
void installJSIBindingsWithRuntime(jsi::Runtime &runtime) override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_animated_SRC CONFIGURE_DEPENDS
*.cpp
drivers/*.cpp
event_drivers/*.cpp
internal/*.cpp
nodes/*.cpp)
add_library(react_renderer_animated OBJECT ${react_renderer_animated_SRC})
target_include_directories(react_renderer_animated PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_animated
react_codegen_rncore
react_debug
react_renderer_core
react_renderer_graphics
react_renderer_mounting
react_renderer_uimanager
react_renderer_scheduler
react_renderer_animationbackend
glog
folly_runtime
)
target_compile_reactnative_options(react_renderer_animated PRIVATE)
target_compile_options(react_renderer_animated PRIVATE -Wpedantic)
target_compile_definitions(react_renderer_animated PRIVATE RN_USE_ANIMATION_BACKEND)

View File

@@ -0,0 +1,71 @@
/*
* 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 <mutex>
#include <shared_mutex>
#include <react/renderer/core/EventPayload.h>
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react {
/**
* Listener for events dispatched to JS runtime.
* Return `true` to interrupt default dispatch to JS event emitter, `false` to
* pass through to default handlers.
*/
template <typename... TArgs>
using EventListenerT = std::function<bool(TArgs...)>;
template <typename... TArgs>
class EventListenerContainerT {
public:
/*
* Invoke listeners in this container with the event.
* Returns true if event was handled by the listener, false to continue
* default dispatch.
*/
bool willDispatchEvent(TArgs... args)
{
std::shared_lock lock(mutex_);
bool handled = false;
for (const auto &listener : eventListeners_) {
handled = (*listener)(args...);
if (handled) {
break;
}
}
return handled;
}
void addListener(std::shared_ptr<const EventListenerT<TArgs...>> listener)
{
std::unique_lock lock(mutex_);
eventListeners_.push_back(std::move(listener));
}
void removeListener(const std::shared_ptr<const EventListenerT<TArgs...>> &listener)
{
std::unique_lock lock(mutex_);
auto it = std::find(eventListeners_.begin(), eventListeners_.end(), listener);
if (it != eventListeners_.end()) {
eventListeners_.erase(it);
}
}
private:
std::shared_mutex mutex_;
std::vector<std::shared_ptr<const EventListenerT<TArgs...>>> eventListeners_;
};
using EventEmitterListener = EventListenerT<Tag, const std::string &, const EventPayload &>;
using EventEmitterListenerContainer = EventListenerContainerT<Tag, const std::string &, const EventPayload &>;
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* 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 "MergedValueDispatcher.h"
#include <react/renderer/core/DynamicPropsUtilities.h>
namespace facebook::react {
MergedValueDispatcher::MergedValueDispatcher(
DispatchFunction dispatchFunction,
MergedValueFunction mergedValueFunction)
: dispatchFunction_(std::move(dispatchFunction)),
mergedValueFunction_(std::move(mergedValueFunction)) {}
void MergedValueDispatcher::dispatch(
const std::unordered_map<Tag, folly::dynamic>& value) {
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [viewTag, props] : value) {
accumulatedValues_[viewTag] = mergeDynamicProps(
accumulatedValues_[viewTag], props, NullValueStrategy::Override);
}
if (hasPendingDispatch_) {
return;
}
hasPendingDispatch_ = true;
}
dispatchFunction_([this]() {
auto accumulatedValuesCopy = std::unordered_map<Tag, folly::dynamic>{};
{
std::lock_guard<std::mutex> lock(mutex_);
std::swap(accumulatedValues_, accumulatedValuesCopy);
hasPendingDispatch_ = false;
}
mergedValueFunction_(std::move(accumulatedValuesCopy));
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* 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 <folly/dynamic.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <functional>
#include <mutex>
namespace facebook::react {
/**
* A thread-safe dispatcher that ensures only the latest value is dispatched
* to avoid overloading the target thread. Multiple rapid calls are coalesced
* into a single dispatch operation merging the values.
*/
class MergedValueDispatcher {
public:
using DispatchFunction = std::function<void(std::function<void()> &&)>;
using MergedValueFunction = std::function<void(std::unordered_map<Tag, folly::dynamic> &&tagToProps)>;
/**
* Creates a MergedValueDispatcher with the given dispatch function.
*
* @param dispatchFunction - function that dispatches to the target
* thread.
* @param latestValueFunction - function that will be called on the target
* thread with all values merged.
*/
explicit MergedValueDispatcher(DispatchFunction dispatchFunction, MergedValueFunction mergedValueFunction);
/**
* Dispatches the given value. If a dispatch is already pending, this will
* merge with the pending value instead of creating a new dispatch.
*
* @param value - value to be dispatched.
*/
void dispatch(const std::unordered_map<Tag, folly::dynamic> &value);
private:
DispatchFunction dispatchFunction_;
MergedValueFunction mergedValueFunction_;
std::mutex mutex_;
bool hasPendingDispatch_{false};
std::unordered_map<Tag, folly::dynamic> accumulatedValues_;
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,268 @@
/*
* 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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <folly/dynamic.h>
#include <react/bridging/Function.h>
#include <react/debug/flags.h>
#include <react/renderer/animated/EventEmitterListener.h>
#include <react/renderer/animated/event_drivers/EventAnimationDriver.h>
#ifdef RN_USE_ANIMATION_BACKEND
#include <react/renderer/animationbackend/AnimationBackend.h>
#endif
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
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<void(double)>;
using UiTask = std::function<void()>;
using EndResult = NativeAnimatedTurboModuleEndResult<bool, std::optional<double>, std::optional<double>>;
using AnimationEndCallback = AsyncCallback<EndResult>;
template <>
struct Bridging<EndResult> : NativeAnimatedTurboModuleEndResultBridging<EndResult> {};
class NativeAnimatedNodesManager {
public:
using DirectManipulationCallback = std::function<void(Tag, const folly::dynamic &)>;
using FabricCommitCallback = std::function<void(std::unordered_map<Tag, folly::dynamic> &)>;
using StartOnRenderCallback = std::function<void(std::function<void()> &&, bool isAsync)>;
using StopOnRenderCallback = std::function<void(bool isAsync)>;
using FrameRateListenerCallback = std::function<void(bool /* shouldEnableListener */)>;
explicit NativeAnimatedNodesManager(
DirectManipulationCallback &&directManipulationCallback,
FabricCommitCallback &&fabricCommitCallback,
StartOnRenderCallback &&startOnRenderCallback = nullptr,
StopOnRenderCallback &&stopOnRenderCallback = nullptr,
FrameRateListenerCallback &&frameRateListenerCallback = nullptr) noexcept;
explicit NativeAnimatedNodesManager(std::shared_ptr<UIManagerAnimationBackend> animationBackend) noexcept;
~NativeAnimatedNodesManager() noexcept;
template <typename T, typename = std::enable_if_t<std::is_base_of_v<AnimatedNode, T>>>
T *getAnimatedNode(Tag tag) const
requires(std::is_base_of_v<AnimatedNode, T>)
{
if (auto it = animatedNodes_.find(tag); it != animatedNodes_.end()) {
return static_cast<T *>(it->second.get());
}
return nullptr;
}
std::optional<double> 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<AnimationEndCallback> 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<EventEmitterListener> 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<std::mutex> 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<int> &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<EventEmitterListener> ensureEventEmitterListener() noexcept;
void handleAnimatedEvent(Tag tag, const std::string &eventName, const EventPayload &payload) noexcept;
std::weak_ptr<UIManagerAnimationBackend> animationBackend_;
std::unique_ptr<AnimatedNode> animatedNode(Tag tag, const folly::dynamic &config) noexcept;
static thread_local bool isOnRenderThread_;
std::mutex animatedNodesCreatedAsyncMutex_;
std::unordered_map<Tag, std::unique_ptr<AnimatedNode>> animatedNodesCreatedAsync_;
std::unordered_map<Tag, std::unique_ptr<AnimatedNode>> animatedNodes_;
std::unordered_map<Tag, Tag> connectedAnimatedNodes_;
std::unordered_map<int, std::unique_ptr<AnimationDriver>> activeAnimations_;
std::unordered_map<
EventAnimationDriverKey,
std::vector<std::unique_ptr<EventAnimationDriver>>,
std::hash<facebook::react::EventAnimationDriverKey>>
eventDrivers_;
std::unordered_set<Tag> updatedNodeTags_;
mutable std::mutex connectedAnimatedNodesMutex_;
std::mutex uiTasksMutex_;
std::vector<UiTask> 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> eventEmitterListener_{nullptr};
std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
std::unordered_map<Tag, folly::dynamic> 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<Tag, folly::dynamic> unsyncedDirectViewProps_{};
int animatedGraphBFSColor_ = 0;
#ifdef REACT_NATIVE_DEBUG
bool warnedAboutGraphTraversal_ = false;
#endif
friend class ColorAnimatedNode;
friend class AnimationDriver;
friend class AnimationTestsBase;
};
} // namespace facebook::react

View File

@@ -0,0 +1,181 @@
/*
* 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 "NativeAnimatedNodesManagerProvider.h"
#include <glog/logging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/animated/MergedValueDispatcher.h>
#include <react/renderer/animated/internal/AnimatedMountingOverrideDelegate.h>
#ifdef RN_USE_ANIMATION_BACKEND
#include <react/renderer/animationbackend/AnimationBackend.h>
#endif
#include <react/renderer/uimanager/UIManagerBinding.h>
namespace facebook::react {
UIManagerNativeAnimatedDelegateImpl::UIManagerNativeAnimatedDelegateImpl(
std::weak_ptr<NativeAnimatedNodesManager> manager)
: nativeAnimatedNodesManager_(manager) {}
void UIManagerNativeAnimatedDelegateImpl::runAnimationFrame() {
if (auto nativeAnimatedNodesManagerStrong =
nativeAnimatedNodesManager_.lock()) {
nativeAnimatedNodesManagerStrong->onRender();
}
}
NativeAnimatedNodesManagerProvider::NativeAnimatedNodesManagerProvider(
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback,
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback,
NativeAnimatedNodesManager::FrameRateListenerCallback
frameRateListenerCallback)
: eventEmitterListenerContainer_(
std::make_shared<EventEmitterListenerContainer>()),
startOnRenderCallback_(std::move(startOnRenderCallback)),
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
frameRateListenerCallback_(std::move(frameRateListenerCallback)) {}
std::shared_ptr<NativeAnimatedNodesManager>
NativeAnimatedNodesManagerProvider::getOrCreate(
jsi::Runtime& runtime,
std::shared_ptr<CallInvoker> jsInvoker) {
if (nativeAnimatedNodesManager_ == nullptr) {
auto* uiManager = &UIManagerBinding::getBinding(runtime)->getUIManager();
NativeAnimatedNodesManager::FabricCommitCallback fabricCommitCallback =
nullptr;
if (!ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated()) {
mergedValueDispatcher_ = std::make_unique<MergedValueDispatcher>(
[jsInvoker](std::function<void()>&& func) {
jsInvoker->invokeAsync(std::move(func));
},
[uiManager](std::unordered_map<Tag, folly::dynamic>&& tagToProps) {
uiManager->updateShadowTree(std::move(tagToProps));
});
fabricCommitCallback =
[this](std::unordered_map<Tag, folly::dynamic>& tagToProps) {
mergedValueDispatcher_->dispatch(tagToProps);
};
}
auto directManipulationCallback =
[uiManager](Tag viewTag, const folly::dynamic& props) {
uiManager->synchronouslyUpdateViewOnUIThread(viewTag, props);
};
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<AnimationBackend>(
std::move(startOnRenderCallback_),
std::move(stopOnRenderCallback_),
std::move(directManipulationCallback),
std::move(fabricCommitCallback),
uiManager);
#endif
nativeAnimatedNodesManager_ =
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
uiManager->unstable_setAnimationBackend(animationBackend_);
} else {
nativeAnimatedNodesManager_ =
std::make_shared<NativeAnimatedNodesManager>(
std::move(directManipulationCallback),
std::move(fabricCommitCallback),
std::move(startOnRenderCallback_),
std::move(stopOnRenderCallback_));
}
addEventEmitterListener(
nativeAnimatedNodesManager_->getEventEmitterListener());
uiManager->addEventListener(
std::make_shared<EventListener>(
[eventEmitterListenerContainerWeak =
std::weak_ptr<EventEmitterListenerContainer>(
eventEmitterListenerContainer_)](
const RawEvent& rawEvent) {
const auto& eventTarget = rawEvent.eventTarget;
const auto& eventPayload = rawEvent.eventPayload;
if (eventTarget && eventPayload) {
if (auto eventEmitterListenerContainer =
eventEmitterListenerContainerWeak.lock();
eventEmitterListenerContainer != nullptr) {
return eventEmitterListenerContainer->willDispatchEvent(
eventTarget->getTag(), rawEvent.type, *eventPayload);
}
}
return false;
}));
nativeAnimatedDelegate_ =
std::make_shared<UIManagerNativeAnimatedDelegateImpl>(
nativeAnimatedNodesManager_);
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
// TODO: remove force casting.
auto* scheduler = (Scheduler*)uiManager->getDelegate();
animatedMountingOverrideDelegate_ =
std::make_shared<AnimatedMountingOverrideDelegate>(
*nativeAnimatedNodesManager_, *scheduler);
// Register on existing surfaces
uiManager->getShadowTreeRegistry().enumerate(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
// Register on surfaces started in the future
uiManager->setOnSurfaceStartCallback(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
}
return nativeAnimatedNodesManager_;
}
void NativeAnimatedNodesManagerProvider::addEventEmitterListener(
const std::shared_ptr<EventEmitterListener>& listener) {
eventEmitterListenerContainer_->addListener(listener);
}
std::shared_ptr<EventEmitterListener>
NativeAnimatedNodesManagerProvider::getEventEmitterListener() {
if (!eventEmitterListener_) {
eventEmitterListener_ = std::make_shared<EventEmitterListener>(
[eventEmitterListenerContainerWeak =
std::weak_ptr<EventEmitterListenerContainer>(
eventEmitterListenerContainer_)](
Tag tag,
const std::string& eventName,
const EventPayload& payload) -> bool {
if (auto eventEmitterListenerContainer =
eventEmitterListenerContainerWeak.lock();
eventEmitterListenerContainer != nullptr) {
return eventEmitterListenerContainer->willDispatchEvent(
tag, eventName, payload);
}
return false;
});
}
return eventEmitterListener_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* 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/animated/MergedValueDispatcher.h>
#include <react/renderer/uimanager/UIManagerNativeAnimatedDelegate.h>
#include "NativeAnimatedNodesManager.h"
namespace facebook::react {
class AnimatedMountingOverrideDelegate;
class NativeAnimatedNodesManagerProvider {
public:
NativeAnimatedNodesManagerProvider(
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback = nullptr,
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback = nullptr,
NativeAnimatedNodesManager::FrameRateListenerCallback frameRateListenerCallback = nullptr);
std::shared_ptr<NativeAnimatedNodesManager> getOrCreate(
jsi::Runtime &runtime,
std::shared_ptr<CallInvoker> jsInvoker);
// Native Event Listeners
void addEventEmitterListener(const std::shared_ptr<EventEmitterListener> &listener);
std::shared_ptr<EventEmitterListener> getEventEmitterListener();
private:
std::shared_ptr<UIManagerAnimationBackend> animationBackend_;
std::shared_ptr<NativeAnimatedNodesManager> nativeAnimatedNodesManager_;
std::shared_ptr<EventEmitterListenerContainer> eventEmitterListenerContainer_;
std::shared_ptr<EventEmitterListener> eventEmitterListener_;
std::shared_ptr<UIManagerNativeAnimatedDelegate> nativeAnimatedDelegate_;
std::shared_ptr<AnimatedMountingOverrideDelegate> animatedMountingOverrideDelegate_;
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback_;
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback_;
NativeAnimatedNodesManager::FrameRateListenerCallback frameRateListenerCallback_;
std::unique_ptr<MergedValueDispatcher> mergedValueDispatcher_;
};
class UIManagerNativeAnimatedDelegateImpl : public UIManagerNativeAnimatedDelegate {
public:
explicit UIManagerNativeAnimatedDelegateImpl(std::weak_ptr<NativeAnimatedNodesManager> manager);
void runAnimationFrame() override;
private:
std::weak_ptr<NativeAnimatedNodesManager> nativeAnimatedNodesManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,102 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AnimationDriver.h"
#include <glog/logging.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <utility>
namespace facebook::react {
std::optional<AnimationDriverType> AnimationDriver::getDriverTypeByName(
const std::string& driverTypeName) {
if (driverTypeName == "frames") {
return AnimationDriverType::Frames;
} else if (driverTypeName == "spring") {
return AnimationDriverType::Spring;
} else if (driverTypeName == "decay") {
return AnimationDriverType::Decay;
} else {
return std::nullopt;
}
}
AnimationDriver::AnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: endCallback_(std::move(endCallback)),
id_(id),
animatedValueTag_(animatedValueTag),
manager_(manager),
config_(std::move(config)) {
onConfigChanged();
}
void AnimationDriver::startAnimation() {
startFrameTimeMs_ = -1;
isStarted_ = true;
}
void AnimationDriver::stopAnimation(bool /*ignoreCompletedHandlers*/) {
if (auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_);
node != nullptr && endCallback_) {
endCallback_.value().call(
{.finished = true,
.value = node->getRawValue(),
.offset = node->getOffset()});
}
}
void AnimationDriver::runAnimationStep(double renderingTime) {
if (!isStarted_ || isComplete_) {
return;
}
const auto frameTimeMs = renderingTime;
auto restarting = false;
if (startFrameTimeMs_ < 0) {
startFrameTimeMs_ = frameTimeMs;
restarting = true;
}
const auto timeDeltaMs = frameTimeMs - startFrameTimeMs_;
const auto isComplete = update(timeDeltaMs, restarting);
if (isComplete) {
if (iterations_ == -1 || ++currentIteration_ < iterations_) {
startFrameTimeMs_ = -1;
} else {
isComplete_ = true;
}
}
}
void AnimationDriver::updateConfig(folly::dynamic config) {
config_ = std::move(config);
onConfigChanged();
}
void AnimationDriver::onConfigChanged() {
iterations_ = (config_.count("iterations") != 0u)
? static_cast<int>(config_["iterations"].asDouble())
: 1;
isComplete_ = iterations_ == 0;
currentIteration_ = 1;
startFrameTimeMs_ = -1;
}
} // namespace facebook::react

View File

@@ -0,0 +1,95 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <react/debug/flags.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
enum class AnimationDriverType {
Frames,
Spring,
Decay,
};
class AnimationDriver {
public:
AnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
virtual ~AnimationDriver() = default;
void startAnimation();
void stopAnimation(bool ignoreCompletedHandlers = false);
inline int getId() const noexcept
{
return id_;
}
inline Tag getAnimatedValueTag() const noexcept
{
return animatedValueTag_;
}
bool getIsComplete() const noexcept
{
return isComplete_;
}
void runAnimationStep(double renderingTime);
virtual void updateConfig(folly::dynamic config);
#ifdef REACT_NATIVE_DEBUG
std::string debugID() const
{
return (config_.count("debugID") != 0u) ? config_["debugID"].asString() : "";
}
#endif
static std::optional<AnimationDriverType> getDriverTypeByName(const std::string &driverTypeName);
protected:
virtual bool update(double /*timeDeltaMs*/, bool /*restarting*/)
{
return true;
}
void markNodeUpdated(Tag tag)
{
manager_->updatedNodeTags_.insert(tag);
}
std::optional<AnimationEndCallback> endCallback_;
int id_{0};
Tag animatedValueTag_{}; // Tag of a ValueAnimatedNode
int iterations_{0};
NativeAnimatedNodesManager *manager_;
bool isComplete_{false};
int currentIteration_{0};
double startFrameTimeMs_{-1};
bool isStarted_{false};
bool ignoreCompletedHandlers_{false};
folly::dynamic config_{};
private:
void onConfigChanged();
};
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <string_view>
namespace facebook::react {
static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
static constexpr std::string_view ExtrapolateTypeExtend = "extend";
static constexpr double SingleFrameIntervalMs = 1000.0 / 60.0;
static constexpr double TicksPerMs = 10000.0; // ticks are 100 nanoseconds
inline double interpolate(
double inputValue,
double inputMin,
double inputMax,
double outputMin,
double outputMax,
std::string_view extrapolateLeft,
std::string_view extrapolateRight)
{
auto result = inputValue;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft == ExtrapolateTypeIdentity) {
return result;
} else if (extrapolateLeft == ExtrapolateTypeClamp) {
result = inputMin;
}
}
if (result > inputMax) {
if (extrapolateRight == ExtrapolateTypeIdentity) {
return result;
} else if (extrapolateRight == ExtrapolateTypeClamp) {
result = inputMax;
}
}
if (inputMin == inputMax) {
if (inputValue <= inputMin) {
return outputMin;
}
return outputMax;
}
return outputMin + (outputMax - outputMin) * (result - inputMin) / (inputMax - inputMin);
}
} // namespace facebook::react

View File

@@ -0,0 +1,81 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DecayAnimationDriver.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
DecayAnimationDriver::DecayAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager),
velocity_(config_["velocity"].asDouble()),
deceleration_(config_["deceleration"].asDouble()) {
react_native_assert(deceleration_ > 0);
}
std::tuple<float, double> DecayAnimationDriver::getValueAndVelocityForTime(
double time) const {
const auto value = fromValue_.value() +
velocity_ / (1 - deceleration_) *
(1 - std::exp(-(1 - deceleration_) * (1000 * time)));
return std::make_tuple(
static_cast<float>(value),
42.0f); // we don't need the velocity, so set it to a dummy value
}
bool DecayAnimationDriver::update(double timeDeltaMs, bool restarting) {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (restarting) {
const auto value = node->getRawValue();
if (!fromValue_.has_value()) {
// First iteration, assign fromValue based on AnimatedValue
fromValue_ = value;
} else {
// Not the first iteration, reset AnimatedValue based on
// originalValue
if (node->setRawValue(fromValue_.value())) {
markNodeUpdated(node->tag());
}
}
lastValue_ = value;
}
const auto [value, velocity] =
getValueAndVelocityForTime(timeDeltaMs / 1000.0);
auto isComplete =
lastValue_.has_value() && std::abs(value - lastValue_.value()) < 0.1;
if (!restarting && isComplete) {
return true;
} else {
lastValue_ = value;
if (node->setRawValue(value)) {
markNodeUpdated(node->tag());
}
return false;
}
}
return true;
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class DecayAnimationDriver : public AnimationDriver {
public:
DecayAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
private:
std::tuple<float, double> getValueAndVelocityForTime(double time) const;
private:
double velocity_{0};
double deceleration_{0};
std::optional<double> fromValue_{std::nullopt};
std::optional<double> lastValue_{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,113 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "FrameAnimationDriver.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/drivers/AnimationDriver.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <cmath>
#include <utility>
namespace facebook::react {
FrameAnimationDriver::FrameAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager) {
onConfigChanged();
}
void FrameAnimationDriver::updateConfig(folly::dynamic config) {
AnimationDriver::updateConfig(config);
onConfigChanged();
}
void FrameAnimationDriver::onConfigChanged() {
auto frames = config_["frames"];
react_native_assert(frames.type() == folly::dynamic::ARRAY);
for (const auto& frame : frames) {
auto frameValue = frame.asDouble();
frames_.push_back(frameValue);
}
toValue_ = config_["toValue"].asDouble();
}
bool FrameAnimationDriver::update(double timeDeltaMs, bool /*restarting*/) {
if (auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (!startValue_) {
startValue_ = node->getRawValue();
}
const auto startIndex =
static_cast<size_t>(std::round(timeDeltaMs / SingleFrameIntervalMs));
assert(startIndex >= 0);
const auto nextIndex = startIndex + 1;
double nextValue = NAN;
auto isComplete = false;
if (nextIndex >= frames_.size()) {
if (iterations_ == -1 || currentIteration_ < iterations_) {
// Use last frame value, just in case it's different from toValue_
nextValue = startValue_.value() +
frames_[frames_.size() - 1] * (toValue_ - startValue_.value());
} else {
nextValue = toValue_;
}
isComplete = true;
} else {
const auto fromInterval = startIndex * SingleFrameIntervalMs;
const auto toInterval = nextIndex * SingleFrameIntervalMs;
const auto fromValue = frames_[startIndex];
const auto toValue = frames_[nextIndex];
// Map timestamp to frame value (frames_ elements are in [0,1])
const auto frameOutput = interpolate(
timeDeltaMs,
fromInterval,
toInterval,
fromValue,
toValue,
ExtrapolateTypeExtend,
ExtrapolateTypeExtend);
// Map frame to output value
nextValue = interpolate(
frameOutput,
0,
1,
startValue_.value(),
toValue_,
ExtrapolateTypeExtend,
ExtrapolateTypeExtend);
}
if (node->setRawValue(nextValue)) {
markNodeUpdated(node->tag());
}
return isComplete;
}
return true;
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class FrameAnimationDriver : public AnimationDriver {
public:
FrameAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
void updateConfig(folly::dynamic config) override;
private:
void onConfigChanged();
std::vector<double> frames_{};
double toValue_{0};
std::optional<double> startValue_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,148 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "SpringAnimationDriver.h"
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
static constexpr auto MaxDeltaTimeMs = 4.0 * 1000.0 / 60.0;
SpringAnimationDriver::SpringAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager),
springStiffness_(config_["stiffness"].asDouble()),
springDamping_(config_["damping"].asDouble()),
springMass_(config_["mass"].asDouble()),
initialVelocity_(config_["initialVelocity"].asDouble()),
endValue_(config_["toValue"].asDouble()),
restSpeedThreshold_(config_["restSpeedThreshold"].asDouble()),
displacementFromRestThreshold_(
config_["restDisplacementThreshold"].asDouble()),
overshootClampingEnabled_(config_["overshootClamping"].asBool()) {}
std::tuple<float, double> SpringAnimationDriver::getValueAndVelocityForTime(
double time) const {
const auto startValue = fromValue_.value();
const auto toValue = endValue_;
const auto c = springDamping_;
const auto m = springMass_;
const auto k = springStiffness_;
const auto v0 = -initialVelocity_;
const auto zeta = c / (2 * std::sqrt(k * m));
const auto omega0 = std::sqrt(k / m);
const auto omega1 = omega0 * std::sqrt(1.0 - (zeta * zeta));
const auto x0 = toValue - startValue;
if (zeta < 1) {
const auto envelope = std::exp(-zeta * omega0 * time);
const auto value = static_cast<float>(
toValue -
envelope *
((v0 + zeta * omega0 * x0) / omega1 * std::sin(omega1 * time) +
x0 * std::cos(omega1 * time)));
const auto velocity = zeta * omega0 * envelope *
(std::sin(omega1 * time) * (v0 + zeta * omega0 * x0) / omega1 +
x0 * std::cos(omega1 * time)) -
envelope *
(std::cos(omega1 * time) * (v0 + zeta * omega0 * x0) -
omega1 * x0 * std::sin(omega1 * time));
return std::make_tuple(value, velocity);
} else {
const auto envelope = std::exp(-omega0 * time);
const auto value = static_cast<float>(
endValue_ - envelope * (x0 + (v0 + omega0 * x0) * time));
const auto velocity =
envelope * (v0 * (time * omega0 - 1) + time * x0 * (omega0 * omega0));
return std::make_tuple(value, velocity);
}
}
bool SpringAnimationDriver::update(double timeDeltaMs, bool restarting) {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (restarting) {
if (!fromValue_.has_value()) {
fromValue_ = node->getRawValue();
} else {
if (node->setRawValue(fromValue_.value())) {
markNodeUpdated(node->tag());
}
}
// Spring animations run a frame behind JS driven animations if we do
// not start the first frame at 16ms.
lastTime_ = timeDeltaMs - SingleFrameIntervalMs;
timeAccumulator_ = 0.0;
}
// clamp the amount of timeDeltaMs to avoid stuttering in the UI.
// We should be able to catch up in a subsequent advance if necessary.
auto adjustedDeltaTime = timeDeltaMs - lastTime_;
if (adjustedDeltaTime > MaxDeltaTimeMs) {
adjustedDeltaTime = MaxDeltaTimeMs;
}
timeAccumulator_ += adjustedDeltaTime;
lastTime_ = timeDeltaMs;
auto [value, velocity] =
getValueAndVelocityForTime(timeAccumulator_ / 1000.0);
auto isComplete = false;
if (isAtRest(velocity, value, endValue_) ||
(overshootClampingEnabled_ && isOvershooting(value))) {
if (springStiffness_ > 0) {
value = static_cast<float>(endValue_);
} else {
endValue_ = value;
}
isComplete = true;
}
if (node->setRawValue(value)) {
markNodeUpdated(node->tag());
}
return isComplete;
}
return true;
}
bool SpringAnimationDriver::isAtRest(
double currentVelocity,
double currentValue,
double endValue) const {
return std::abs(currentVelocity) <= restSpeedThreshold_ &&
(std::abs(currentValue - endValue) <= displacementFromRestThreshold_ ||
springStiffness_ == 0);
}
bool SpringAnimationDriver::isOvershooting(double currentValue) const {
const auto startValue = fromValue_.value();
return springStiffness_ > 0 &&
((startValue < endValue_ && currentValue > endValue_) ||
(startValue > endValue_ && currentValue < endValue_));
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class SpringAnimationDriver : public AnimationDriver {
public:
SpringAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
private:
std::tuple<float, double> getValueAndVelocityForTime(double time) const;
bool isAtRest(double currentVelocity, double currentValue, double endValue) const;
bool isOvershooting(double currentValue) const;
double springStiffness_{0};
double springDamping_{0};
double springMass_{0};
double initialVelocity_{0};
std::optional<double> fromValue_{std::nullopt};
double endValue_{0};
double restSpeedThreshold_{0};
double displacementFromRestThreshold_{0};
bool overshootClampingEnabled_{false};
double lastTime_{0};
double timeAccumulator_{0};
};
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "EventAnimationDriver.h"
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
EventAnimationDriver::EventAnimationDriver(
const std::vector<std::string>& eventPath,
Tag animatedValueTag)
: eventPath_(eventPath), animatedValueTag_(animatedValueTag) {}
std::optional<double> EventAnimationDriver::getValueFromPayload(
const EventPayload& eventPayload) {
return eventPayload.extractValue(eventPath_);
}
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/renderer/core/EventPayload.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <optional>
#include <string>
#include <vector>
namespace facebook::react {
class EventAnimationDriver {
public:
EventAnimationDriver(const std::vector<std::string> &eventPath, Tag animatedValueTag);
~EventAnimationDriver() = default;
std::optional<double> getValueFromPayload(const EventPayload &eventPayload);
Tag getAnimatedNodeTag() const
{
return animatedValueTag_;
}
protected:
std::vector<std::string> eventPath_;
const Tag animatedValueTag_;
};
struct EventAnimationDriverKey {
Tag viewTag;
std::string eventName;
bool operator==(const facebook::react::EventAnimationDriverKey &rhs) const noexcept
{
return viewTag == rhs.viewTag && eventName == rhs.eventName;
}
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::EventAnimationDriverKey> {
size_t operator()(const facebook::react::EventAnimationDriverKey &key) const
{
return std::hash<facebook::react::Tag>()(key.viewTag) ^ std::hash<std::string>()(key.eventName);
}
};
} // namespace std

View File

@@ -0,0 +1,106 @@
/*
* 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 "AnimatedMountingOverrideDelegate.h"
#include "../NativeAnimatedNodesManager.h"
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/DynamicPropsUtilities.h>
#include <react/renderer/scheduler/Scheduler.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
namespace facebook::react {
AnimatedMountingOverrideDelegate::AnimatedMountingOverrideDelegate(
NativeAnimatedNodesManager& animatedManager,
const Scheduler& scheduler)
: MountingOverrideDelegate(),
animatedManager_(&animatedManager),
scheduler_(&scheduler) {};
bool AnimatedMountingOverrideDelegate::shouldOverridePullTransaction() const {
if (animatedManager_ != nullptr) {
return animatedManager_->hasManagedProps();
}
return false;
}
std::optional<MountingTransaction>
AnimatedMountingOverrideDelegate::pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number transactionNumber,
const TransactionTelemetry& telemetry,
ShadowViewMutationList mutations) const {
std::unordered_map<Tag, folly::dynamic> animatedManagedProps;
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Update) {
const auto tag = mutation.newChildShadowView.tag;
auto props = animatedManager_->managedProps(tag);
if (!props.isNull()) {
animatedManagedProps.insert({tag, std::move(props)});
}
} else if (mutation.type == ShadowViewMutation::Delete) {
animatedManager_->onManagedPropsRemoved(mutation.oldChildShadowView.tag);
}
}
if (animatedManagedProps.empty()) {
return MountingTransaction{
surfaceId, transactionNumber, std::move(mutations), telemetry};
}
ShadowViewMutation::List filteredMutations;
filteredMutations.reserve(mutations.size());
for (const auto& mutation : mutations) {
folly::dynamic modifiedProps = folly::dynamic::object();
if (mutation.type == ShadowViewMutation::Update) {
if (auto node =
animatedManagedProps.extract(mutation.newChildShadowView.tag)) {
modifiedProps = std::move(node.mapped());
}
}
if (modifiedProps.empty()) {
filteredMutations.push_back(mutation);
} else {
if (const auto* componentDescriptor =
scheduler_
->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
mutation.newChildShadowView.componentHandle)) {
PropsParserContext propsParserContext{
mutation.newChildShadowView.surfaceId,
*scheduler_->getContextContainer()};
auto modifiedNewChildShadowView = mutation.newChildShadowView;
modifiedNewChildShadowView.props = componentDescriptor->cloneProps(
propsParserContext,
mutation.newChildShadowView.props,
RawProps(modifiedProps));
#ifdef RN_SERIALIZABLE_STATE
// Until Props 2.0 is shipped, android uses rawProps.
// RawProps must be kept synced with C++ Animated as well
// as props object.
auto& castedProps =
const_cast<Props&>(*modifiedNewChildShadowView.props);
castedProps.rawProps = mergeDynamicProps(
mutation.newChildShadowView.props->rawProps,
modifiedProps,
NullValueStrategy::Override);
#endif
filteredMutations.emplace_back(
ShadowViewMutation::UpdateMutation(
mutation.oldChildShadowView,
std::move(modifiedNewChildShadowView),
mutation.parentTag));
}
}
}
return MountingTransaction{
surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
}
} // namespace facebook::react

View File

@@ -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 <folly/dynamic.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <functional>
#include <optional>
namespace facebook::react {
class Scheduler;
class NativeAnimatedNodesManager;
class AnimatedMountingOverrideDelegate : public MountingOverrideDelegate {
public:
AnimatedMountingOverrideDelegate(NativeAnimatedNodesManager &animatedManager, const Scheduler &scheduler);
bool shouldOverridePullTransaction() const override;
std::optional<MountingTransaction> pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number transactionNumber,
const TransactionTelemetry &telemetry,
ShadowViewMutationList mutations) const override;
private:
mutable NativeAnimatedNodesManager *animatedManager_;
const Scheduler *scheduler_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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 <string>
#include <unordered_set>
namespace facebook::react {
inline static std::unordered_set<std::string> getDirectManipulationAllowlist()
{
/**
* Direct manipulation eligible styles allowed by the NativeAnimated JS
* implementation. Keep in sync with
* packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js
*/
static std::unordered_set<std::string> DIRECT_MANIPULATION_STYLES{
/* SUPPORTED_COLOR_STYLES */
"backgroundColor",
"borderBottomColor",
"borderColor",
"borderEndColor",
"borderLeftColor",
"borderRightColor",
"borderStartColor",
"borderTopColor",
"color",
"tintColor",
/* SUPPORTED STYLES */
"borderBottomEndRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
"borderBottomStartRadius",
"borderEndEndRadius",
"borderEndStartRadius",
"borderRadius",
"borderTopEndRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"borderTopStartRadius",
"borderStartEndRadius",
"borderStartStartRadius",
"elevation",
"opacity",
"transform",
"zIndex",
/* ios styles */
"shadowOpacity",
"shadowRadius",
/* legacy android transform properties */
"scaleX",
"scaleY",
"translateX",
"translateY",
};
return DIRECT_MANIPULATION_STYLES;
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* 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/core/ReactPrimitives.h>
namespace facebook::react::animated {
// Indicates that the animated node identifier is not defined.
// It is safe to use 0 because JavaScript starts assigning identifiers from 1.
// https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/animated/NativeAnimatedHelper.js#L35
constexpr static Tag undefinedAnimatedNodeIdentifier = 0;
} // namespace facebook::react::animated

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AdditionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void AdditionAnimatedNode::update() {
auto rawValue = 0.0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for AdditionAnimatedNode");
rawValue += node->getValue();
}
setRawValue(rawValue);
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class AdditionAnimatedNode final : public OperatorAnimatedNode {
public:
AdditionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,83 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <utility>
namespace facebook::react {
AnimatedNode::AnimatedNode(
Tag tag,
folly::dynamic config,
NativeAnimatedNodesManager& manager,
AnimatedNodeType type)
: tag_(tag), manager_(&manager), type_(type), config_(std::move(config)) {}
void AnimatedNode::addChild(const Tag animatedNodeTag) {
children_.insert(animatedNodeTag);
getChildNode(animatedNodeTag)->onAttachToNode(tag_);
}
void AnimatedNode::removeChild(const Tag tag) {
if (const auto childNode = getChildNode(tag)) {
childNode->onDetachedFromNode(tag_);
children_.erase(tag);
}
}
AnimatedNode* AnimatedNode::getChildNode(Tag tag) {
if (children_.find(tag) != children_.end()) {
return manager_->getAnimatedNode<AnimatedNode>(tag);
}
return nullptr;
}
std::optional<AnimatedNodeType> AnimatedNode::getNodeTypeByName(
const std::string& nodeTypeName) {
if (nodeTypeName == "style") {
return AnimatedNodeType::Style;
} else if (nodeTypeName == "value") {
return AnimatedNodeType::Value;
} else if (nodeTypeName == "color") {
return AnimatedNodeType::Color;
} else if (nodeTypeName == "props") {
return AnimatedNodeType::Props;
} else if (nodeTypeName == "interpolation") {
return AnimatedNodeType::Interpolation;
} else if (nodeTypeName == "addition") {
return AnimatedNodeType::Addition;
} else if (nodeTypeName == "subtraction") {
return AnimatedNodeType::Subtraction;
} else if (nodeTypeName == "division") {
return AnimatedNodeType::Division;
} else if (nodeTypeName == "multiplication") {
return AnimatedNodeType::Multiplication;
} else if (nodeTypeName == "modulus") {
return AnimatedNodeType::Modulus;
} else if (nodeTypeName == "diffclamp") {
return AnimatedNodeType::Diffclamp;
} else if (nodeTypeName == "transform") {
return AnimatedNodeType::Transform;
} else if (nodeTypeName == "tracking") {
return AnimatedNodeType::Tracking;
} else if (nodeTypeName == "round") {
return AnimatedNodeType::Round;
} else if (nodeTypeName == "object") {
return AnimatedNodeType::Object;
} else {
return std::nullopt;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,112 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/debug/flags.h>
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react {
enum class AnimatedNodeType {
Style,
Value,
Props,
Interpolation,
Addition,
Subtraction,
Division,
Multiplication,
Modulus,
Diffclamp,
Transform,
Tracking,
Color,
Round,
Object
};
class NativeAnimatedNodesManager;
class AnimatedNode {
public:
AnimatedNode(
Tag tag,
folly::dynamic config,
// TODO: T190028913 maybe pass in strongly typed data when constructing
// AnimatedNode
NativeAnimatedNodesManager &manager,
AnimatedNodeType type);
// Detach Node
virtual ~AnimatedNode() = default;
AnimatedNode(AnimatedNode &&) noexcept = default;
AnimatedNode &operator=(AnimatedNode &&) noexcept = default;
AnimatedNode(const AnimatedNode &) = default;
AnimatedNode &operator=(const AnimatedNode &) = default;
Tag tag() const noexcept
{
return tag_;
}
void addChild(Tag tag);
void removeChild(Tag tag);
const std::unordered_set<Tag> &getChildren() const noexcept
{
return children_;
}
AnimatedNodeType type() const noexcept
{
return type_;
}
const folly::dynamic &getConfig() const noexcept
{
return config_;
}
#ifdef REACT_NATIVE_DEBUG
std::string debugID() const
{
return (getConfig().count("debugID") != 0u) ? getConfig()["debugID"].asString() : "";
}
#endif
virtual void update() {}
virtual void onDetachedFromNode(Tag /*animatedNodeTag*/) {}
virtual void onAttachToNode(Tag /*animatedNodeTag*/) {}
static std::optional<AnimatedNodeType> getNodeTypeByName(const std::string &nodeTypeName);
int activeIncomingNodes = 0;
int bfsColor = 0;
static constexpr int INITIAL_BFS_COLOR = 0;
protected:
AnimatedNode *getChildNode(Tag tag);
Tag tag_{0};
NativeAnimatedNodesManager *manager_;
AnimatedNodeType type_;
std::unordered_set<Tag> children_{};
private:
// Should remain unchanged after initialized in constructor
folly::dynamic config_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ColorAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
uint8_t getColorValue(
const NativeAnimatedNodesManager& manager,
Tag nodeTag,
bool isDecimal = false) {
if (const auto node = manager.getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (isDecimal) {
return std::clamp(
static_cast<uint32_t>(node->getValue() * 255), 0u, 255u);
} else {
return std::clamp(static_cast<uint32_t>(node->getValue()), 0u, 255u);
}
}
return 0;
}
uint8_t getAlphaValue(const NativeAnimatedNodesManager& manager, Tag nodeTag) {
return getColorValue(manager, nodeTag, true);
}
} // namespace
ColorAnimatedNode::ColorAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Color),
rNodeTag_(static_cast<Tag>(getConfig()["r"].asInt())),
gNodeTag_(static_cast<Tag>(getConfig()["g"].asInt())),
bNodeTag_(static_cast<Tag>(getConfig()["b"].asInt())),
aNodeTag_(static_cast<Tag>(getConfig()["a"].asInt())) {}
void ColorAnimatedNode::update() {
color_ = *colorFromRGBA(
getColorValue(*manager_, rNodeTag_),
getColorValue(*manager_, gNodeTag_),
getColorValue(*manager_, bNodeTag_),
getAlphaValue(*manager_, aNodeTag_));
}
Color ColorAnimatedNode::getColor() {
if (manager_->updatedNodeTags_.contains(tag_)) {
update();
manager_->updatedNodeTags_.erase(tag_);
}
return color_;
return 0;
}
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class ColorAnimatedNode final : public AnimatedNode {
public:
ColorAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
Color getColor();
private:
Tag rNodeTag_{};
Tag gNodeTag_{};
Tag bNodeTag_{};
Tag aNodeTag_{};
Color color_{0};
};
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DiffClampAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
DiffClampAnimatedNode::DiffClampAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asDouble())),
min_(getConfig()["min"].asDouble()),
max_(getConfig()["max"].asDouble()) {}
void DiffClampAnimatedNode::update() {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_)) {
const auto value = node->getValue();
const auto diff = value - lastValue_;
lastValue_ = value;
setRawValue(std::clamp(this->getValue() + diff, min_, max_));
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class DiffClampAnimatedNode final : public ValueAnimatedNode {
public:
DiffClampAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_;
double min_;
double max_;
double lastValue_{0};
};
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DivisionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void DivisionAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for DivisionAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue /= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class DivisionAnimatedNode final : public OperatorAnimatedNode {
public:
DivisionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "InterpolationAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/internal/primitives.h>
#include <react/renderer/graphics/HostPlatformColor.h>
namespace facebook::react {
InterpolationAnimatedNode::InterpolationAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager) {
// inputRange example: [0, 1, 10], [1, 1.4, 1.5]
const auto& nodeConfig = getConfig();
for (const auto& rangeValue : nodeConfig["inputRange"]) {
inputRanges_.push_back(rangeValue.asDouble());
}
const bool isColorOutput = nodeConfig["outputType"].isString() &&
nodeConfig["outputType"].asString() == "color";
if (isColorOutput) {
isColorValue_ = true;
for (const auto& rangeValue : nodeConfig["outputRange"]) {
colorOutputRanges_.push_back(static_cast<int>(rangeValue.asInt()));
}
} else {
for (const auto& rangeValue : nodeConfig["outputRange"]) {
defaultOutputRanges_.push_back(rangeValue.asDouble());
}
}
extrapolateLeft_ = nodeConfig["extrapolateLeft"].asString();
extrapolateRight_ = nodeConfig["extrapolateRight"].asString();
}
void InterpolationAnimatedNode::update() {
if (parentTag_ == animated::undefinedAnimatedNodeIdentifier) {
return;
}
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(parentTag_)) {
if (isColorValue_) {
setRawValue(interpolateColor(node->getValue()));
} else {
setRawValue(interpolateValue(node->getValue()));
}
}
}
void InterpolationAnimatedNode::onDetachedFromNode(Tag animatedNodeTag) {
assert(parentTag_ == animatedNodeTag);
parentTag_ = animated::undefinedAnimatedNodeIdentifier;
}
void InterpolationAnimatedNode::onAttachToNode(Tag animatedNodeTag) {
assert(!parentTag_);
parentTag_ = animatedNodeTag;
}
double InterpolationAnimatedNode::interpolateValue(double value) {
// Compute range index
int index = 1;
for (; index < inputRanges_.size() - 1; ++index) {
if (inputRanges_[index] >= value) {
break;
}
}
index--;
return interpolate(
value,
inputRanges_[index],
inputRanges_[index + 1],
defaultOutputRanges_[index],
defaultOutputRanges_[index + 1],
extrapolateLeft_,
extrapolateRight_);
}
double InterpolationAnimatedNode::interpolateColor(double value) {
// Compute range index
int index = 1;
for (; index < inputRanges_.size() - 1; ++index) {
if (inputRanges_[index] >= value) {
break;
}
}
index--;
const auto outputMin = colorOutputRanges_[index];
const auto outputMax = colorOutputRanges_[index + 1];
if (outputMin == outputMax) {
return outputMin;
}
const auto inputMin = inputRanges_[index];
const auto inputMax = inputRanges_[index + 1];
if (inputMin == inputMax) {
if (value <= inputMin) {
return static_cast<int32_t>(outputMin);
} else {
return static_cast<int32_t>(outputMax);
}
}
auto ratio = (value - inputMin) / (inputMax - inputMin);
auto outputMinA = alphaFromHostPlatformColor(outputMin);
auto outputMinR = redFromHostPlatformColor(outputMin);
auto outputMinG = greenFromHostPlatformColor(outputMin);
auto outputMinB = blueFromHostPlatformColor(outputMin);
auto outputMaxA = alphaFromHostPlatformColor(outputMax);
auto outputMaxR = redFromHostPlatformColor(outputMax);
auto outputMaxG = greenFromHostPlatformColor(outputMax);
auto outputMaxB = blueFromHostPlatformColor(outputMax);
auto outputValueA = ratio * (outputMaxA - outputMinA) + outputMinA;
auto outputValueR = ratio * (outputMaxR - outputMinR) + outputMinR;
auto outputValueG = ratio * (outputMaxG - outputMinG) + outputMinG;
auto outputValueB = ratio * (outputMaxB - outputMinB) + outputMinB;
return static_cast<int32_t>(hostPlatformColorFromRGBA(
static_cast<uint8_t>(outputValueR),
static_cast<uint8_t>(outputValueG),
static_cast<uint8_t>(outputValueB),
static_cast<uint8_t>(outputValueA)));
}
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
#include <react/renderer/animated/internal/primitives.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class InterpolationAnimatedNode final : public ValueAnimatedNode {
public:
InterpolationAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
void onDetachedFromNode(Tag animatedNodeTag) override;
void onAttachToNode(Tag animatedNodeTag) override;
private:
double interpolateValue(double value);
double interpolateColor(double value);
std::vector<double> inputRanges_;
std::vector<double> defaultOutputRanges_;
std::vector<Color> colorOutputRanges_;
std::string extrapolateLeft_;
std::string extrapolateRight_;
Tag parentTag_{animated::undefinedAnimatedNodeIdentifier};
};
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ModulusAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
ModulusAnimatedNode::ModulusAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asInt())),
modulus_(getConfig()["modulus"].asDouble()) {}
void ModulusAnimatedNode::update() {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_)) {
setRawValue(std::fmod(node->getValue(), modulus_));
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class ModulusAnimatedNode final : public ValueAnimatedNode {
public:
ModulusAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_{};
double modulus_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "MultiplicationAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void MultiplicationAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for MultiplicationAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue *= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class MultiplicationAnimatedNode final : public OperatorAnimatedNode {
public:
MultiplicationAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,121 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ObjectAnimatedNode.h"
#include <glog/logging.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/internal/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
ObjectAnimatedNode::ObjectAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Object) {}
void ObjectAnimatedNode::collectViewUpdates(
std::string propKey,
folly::dynamic& props) {
const auto& value = getConfig()["value"];
switch (value.type()) {
case folly::dynamic::OBJECT: {
props.insert(propKey, collectViewUpdatesObjectHelper(value));
} break;
case folly::dynamic::ARRAY: {
props.insert(propKey, collectViewUpdatesArrayHelper(value));
} break;
default: {
LOG(ERROR) << "Invalid value type for ObjectAnimatedNode";
} break;
}
}
folly::dynamic ObjectAnimatedNode::collectViewUpdatesObjectHelper(
const folly::dynamic& value) const {
folly::dynamic result = folly::dynamic::object();
for (const auto& valueProp : value.items()) {
result.insert(valueProp.first.asString(), getValueProp(valueProp.second));
}
return result;
}
folly::dynamic ObjectAnimatedNode::collectViewUpdatesArrayHelper(
const folly::dynamic& value) const {
folly::dynamic result = folly::dynamic::array();
for (const auto& valueProp : value) {
result.push_back(getValueProp(valueProp));
}
return result;
}
folly::dynamic ObjectAnimatedNode::getValueProp(
const folly::dynamic& prop) const {
switch (prop.type()) {
case folly::dynamic::OBJECT: {
if (auto itNodeTag = prop.find("nodeTag");
itNodeTag != prop.items().end()) {
auto nodeTag = static_cast<Tag>(itNodeTag->second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
return static_cast<int32_t>(valueNode->getValue());
} else {
return valueNode->getValue();
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto colorAnimNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
return static_cast<int32_t>(colorAnimNode->getColor());
}
} break;
default:
break;
}
}
} else {
return collectViewUpdatesObjectHelper(prop);
}
} break;
case folly::dynamic::ARRAY: {
return collectViewUpdatesArrayHelper(prop);
};
case folly::dynamic::NULLT:
case folly::dynamic::BOOL:
case folly::dynamic::DOUBLE:
case folly::dynamic::INT64:
case folly::dynamic::STRING: {
return prop;
};
}
LOG(ERROR) << "Invalid prop type for ObjectAnimatedNode";
return nullptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <folly/dynamic.h>
namespace facebook::react {
class ObjectAnimatedNode final : public AnimatedNode {
public:
ObjectAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(std::string propKey, folly::dynamic &props);
private:
folly::dynamic collectViewUpdatesObjectHelper(const folly::dynamic &value) const;
folly::dynamic collectViewUpdatesArrayHelper(const folly::dynamic &value) const;
folly::dynamic getValueProp(const folly::dynamic &prop) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,150 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "PropsAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/animated/nodes/StyleAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
bool isLayoutStyleUpdated(
const folly::dynamic& props,
NativeAnimatedNodesManager& manager) {
for (const auto& entry : props.items()) {
auto nodeTag = static_cast<Tag>(entry.second.asInt());
if (const auto& node = manager.getAnimatedNode<AnimatedNode>(nodeTag)) {
if (node->type() == AnimatedNodeType::Style) {
if (const auto& styleNode =
manager.getAnimatedNode<StyleAnimatedNode>(nodeTag)) {
if (styleNode->isLayoutStyleUpdated()) {
return true;
}
}
}
}
}
return false;
}
} // namespace
PropsAnimatedNode::PropsAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Props),
props_(folly::dynamic::object()) {}
void PropsAnimatedNode::connectToView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier &&
"Animated node has already been attached to a view already exists.");
connectedViewTag_ = viewTag;
}
void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == viewTag &&
"Attempting to disconnect view that has not been connected with the given animated node.");
connectedViewTag_ = animated::undefinedAnimatedNodeIdentifier;
}
// restore the value to whatever the value was on the ShadowNode instead of in
// the View hierarchy
void PropsAnimatedNode::restoreDefaultValues() {
// If node is already disconnected from View, we cannot restore default values
if (connectedViewTag_ != animated::undefinedAnimatedNodeIdentifier) {
manager_->schedulePropsCommit(
connectedViewTag_, folly::dynamic::object(), false, false);
}
}
void PropsAnimatedNode::update() {
return update(false);
}
void PropsAnimatedNode::update(bool forceFabricCommit) {
if (connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier) {
return;
}
// TODO: T190192206 consolidate shared update logic between
// Props/StyleAnimatedNode
std::lock_guard<std::mutex> lock(propsMutex_);
const auto& configProps = getConfig()["props"];
for (const auto& entry : configProps.items()) {
auto propName = entry.first.asString();
auto nodeTag = static_cast<Tag>(entry.second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto& valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
props_.insert(
propName.c_str(),
static_cast<int32_t>(valueNode->getValue()));
} else {
props_.insert(propName.c_str(), valueNode->getValue());
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto& colorNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
props_.insert(
propName.c_str(), static_cast<int32_t>(colorNode->getColor()));
}
} break;
case AnimatedNodeType::Style: {
if (const auto& styleNode =
manager_->getAnimatedNode<StyleAnimatedNode>(nodeTag)) {
styleNode->collectViewUpdates(props_);
}
} break;
case AnimatedNodeType::Object: {
if (const auto objectNode =
manager_->getAnimatedNode<ObjectAnimatedNode>(nodeTag)) {
objectNode->collectViewUpdates(propName, props_);
}
} break;
case AnimatedNodeType::Props:
case AnimatedNodeType::Tracking:
case AnimatedNodeType::Transform:
break;
}
}
}
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
manager_->schedulePropsCommit(
connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <react/renderer/animated/internal/primitives.h>
#include <mutex>
namespace facebook::react {
class PropsAnimatedNode final : public AnimatedNode {
public:
PropsAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void connectToView(Tag viewTag);
void disconnectFromView(Tag viewTag);
void restoreDefaultValues();
Tag connectedViewTag() const
{
return connectedViewTag_;
}
folly::dynamic props()
{
std::lock_guard<std::mutex> lock(propsMutex_);
return props_;
}
void update() override;
void update(bool forceFabricCommit);
private:
std::mutex propsMutex_;
folly::dynamic props_;
bool layoutStyleUpdated_{false};
Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
};
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "RoundAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
RoundAnimatedNode::RoundAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asInt())),
nearest_(getConfig()["input"].asDouble()) {
react_native_assert(
nearest_ != 0 &&
"'nearest' cannot be 0 (can't round to the nearest multiple of 0)");
}
void RoundAnimatedNode::update() {
auto node = manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_);
react_native_assert(
node && "Illegal node ID set as an input for Animated.round node");
setRawValue(round(node->getValue() / nearest_) * nearest_);
}
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
/**
* Animated node that rounds the value of another animated node to the nearest
* multiple of a given number.
*/
class RoundAnimatedNode : public ValueAnimatedNode {
public:
RoundAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_;
double nearest_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "StyleAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/internal/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
bool isLayoutPropsUpdated(const folly::dynamic& props) {
for (const auto& styleNodeProp : props.items()) {
if (getDirectManipulationAllowlist().count(
styleNodeProp.first.asString()) == 0u) {
return true;
}
}
return false;
}
} // namespace
StyleAnimatedNode::StyleAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Style) {}
void StyleAnimatedNode::collectViewUpdates(folly::dynamic& props) {
const auto& style = getConfig()["style"];
for (const auto& styleProp : style.items()) {
auto propName = styleProp.first.asString();
const auto nodeTag = static_cast<Tag>(styleProp.second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Transform: {
if (const auto transformNode =
manager_->getAnimatedNode<TransformAnimatedNode>(nodeTag)) {
transformNode->collectViewUpdates(props);
}
} break;
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
props.insert(
propName.c_str(),
static_cast<int32_t>(valueNode->getValue()));
} else {
props.insert(propName.c_str(), valueNode->getValue());
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto colorAnimNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
props.insert(
propName.c_str(),
static_cast<int32_t>(colorAnimNode->getColor()));
}
} break;
case AnimatedNodeType::Object: {
if (const auto objectNode =
manager_->getAnimatedNode<ObjectAnimatedNode>(nodeTag)) {
objectNode->collectViewUpdates(propName, props);
}
} break;
case AnimatedNodeType::Tracking:
case AnimatedNodeType::Style:
case AnimatedNodeType::Props:
break;
}
}
}
layoutStyleUpdated_ = isLayoutPropsUpdated(props);
}
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <folly/dynamic.h>
namespace facebook::react {
class StyleAnimatedNode final : public AnimatedNode {
public:
StyleAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(folly::dynamic &props);
bool isLayoutStyleUpdated() const noexcept
{
return layoutStyleUpdated_;
}
private:
bool layoutStyleUpdated_{};
};
} // namespace facebook::react

View File

@@ -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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "SubtractionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void SubtractionAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto& tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for SubtractionAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue -= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include "ValueAnimatedNode.h"
namespace facebook::react {
class SubtractionAnimatedNode final : public OperatorAnimatedNode {
public:
SubtractionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "TrackingAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
TrackingAnimatedNode::TrackingAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Tracking),
animationId_(static_cast<int>(getConfig()["animationId"].asInt())),
toValueNodeId_(static_cast<Tag>(getConfig()["toValue"].asInt())),
valueNodeId_(static_cast<Tag>(getConfig()["value"].asInt())) {}
void TrackingAnimatedNode::update() {
if (const auto toValueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(toValueNodeId_)) {
// In case the animation is already running, we need to stop it to free up
// the animationId key in the active animations map in the animation
// manager.
manager_->stopAnimation(animationId_, true);
auto animationConfig = getConfig()["animationConfig"];
animationConfig["toValue"] = toValueNode->getValue();
manager_->startAnimatingNode(
animationId_, valueNodeId_, animationConfig, std::nullopt);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
namespace facebook::react {
class TrackingAnimatedNode final : public AnimatedNode {
public:
TrackingAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
int animationId_{};
Tag toValueNodeId_{}; // Value node to be tracked
Tag valueNodeId_{}; // Value node to be updated
};
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "TransformAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <utility>
namespace facebook::react {
static constexpr std::string_view sTransformsName{"transforms"};
static constexpr std::string_view sPropertyName{"property"};
static constexpr std::string_view sTypeName{"type"};
static constexpr std::string_view sAnimatedName{"animated"};
static constexpr std::string_view sNodeTagName{"nodeTag"};
static constexpr std::string_view sValueName{"value"};
static constexpr std::string_view sTransformPropName{"transform"};
TransformAnimatedNode::TransformAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Transform) {}
void TransformAnimatedNode::collectViewUpdates(folly::dynamic& props) {
folly::dynamic transforms = folly::dynamic::array();
auto transformsArray = getConfig()[sTransformsName];
react_native_assert(transformsArray.type() == folly::dynamic::ARRAY);
for (const auto& transform : transformsArray) {
std::optional<double> value;
if (transform[sTypeName].asString() == sAnimatedName) {
const auto inputTag = static_cast<Tag>(transform[sNodeTagName].asInt());
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputTag)) {
value = node->getValue();
}
} else {
value = transform[sValueName].asDouble();
}
if (value) {
const auto property = transform[sPropertyName].asString();
transforms.push_back(folly::dynamic::object(property, value.value()));
}
}
props[sTransformPropName] = std::move(transforms);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
namespace facebook::react {
struct TransformConfig {
public:
std::string property;
Tag nodeTag;
double value;
};
class TransformAnimatedNode final : public AnimatedNode {
public:
TransformAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(folly::dynamic &props);
};
} // namespace facebook::react

View File

@@ -0,0 +1,101 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ValueAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
ValueAnimatedNode::ValueAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Value) {
auto value = 0.0;
auto offset = 0.0;
if ((getConfig().count("value") != 0u) &&
(getConfig().count("offset") != 0u)) {
value = getConfig()["value"].asDouble();
offset = getConfig()["offset"].asDouble();
}
value_ = value;
offset_ = offset;
}
bool ValueAnimatedNode::setRawValue(double value) noexcept {
if (value_ != value) {
value_ = value;
onValueUpdate();
return true;
}
return false;
}
double ValueAnimatedNode::getRawValue() const noexcept {
return value_;
}
double ValueAnimatedNode::getOffset() const noexcept {
return offset_;
}
bool ValueAnimatedNode::setOffset(double offset) noexcept {
if (offset_ != offset) {
offset_ = offset;
return true;
}
return true;
}
double ValueAnimatedNode::getValue() const noexcept {
return value_ + getOffset();
}
void ValueAnimatedNode::flattenOffset() noexcept {
value_ = value_ + offset_;
offset_ = 0;
}
void ValueAnimatedNode::extractOffset() noexcept {
offset_ += value_;
value_ = 0;
}
void ValueAnimatedNode::onValueUpdate() noexcept {
if (valueListener_) {
valueListener_(getValue());
}
}
void ValueAnimatedNode::setValueListener(
ValueListenerCallback&& callback) noexcept {
valueListener_ = std::move(callback);
}
OperatorAnimatedNode::OperatorAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager) {
const auto& input = getConfig()["input"];
react_native_assert(
input.type() == folly::dynamic::ARRAY && input.size() >= 2);
for (const auto& inputNode : input) {
const auto inputTag = static_cast<Tag>(inputNode.asInt());
inputNodes_.push_back(inputTag);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <vector>
namespace facebook::react {
using ValueListenerCallback = std::function<void(double)>;
class ValueAnimatedNode : public AnimatedNode {
public:
ValueAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
double getValue() const noexcept;
double getRawValue() const noexcept;
bool setRawValue(double value) noexcept;
double getOffset() const noexcept;
bool setOffset(double offset) noexcept;
void flattenOffset() noexcept;
void extractOffset() noexcept;
void setValueListener(ValueListenerCallback &&callback) noexcept;
bool getIsColorValue() const noexcept
{
return isColorValue_;
}
protected:
bool isColorValue_{false};
private:
void onValueUpdate() noexcept;
double value_{0.0};
double offset_{0.0};
ValueListenerCallback valueListener_{};
};
class OperatorAnimatedNode : public ValueAnimatedNode {
public:
OperatorAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
protected:
std::vector<Tag> inputNodes_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,268 @@
/*
* 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 "AnimationTestsBase.h"
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class AnimatedNodeTests : public AnimationTestsBase {};
TEST_F(AnimatedNodeTests, setAnimatedNodeValue) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto animatedNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
animatedNodeTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 5));
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), true);
runAnimationFrame(0);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), false);
nodesManager_->setAnimatedNodeValue(animatedNodeTag, 100);
// ValueAnimatedNode will immediately update value, before Animated updates
// dirtied nodes at next frame
EXPECT_EQ(nodesManager_->getValue(animatedNodeTag), 105);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), true);
runAnimationFrame(0);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), false);
nodesManager_->dropAnimatedNode(animatedNodeTag);
}
TEST_F(AnimatedNodeTests, updatePropsNode) {
initNodesManager();
// Step 1: Build the Nodes graph
auto rootTag = getNextRootViewTag();
// Create ColorNode
auto rTag = ++rootTag;
auto gTag = ++rootTag;
auto bTag = ++rootTag;
auto aTag = ++rootTag;
auto colorNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
rTag, folly::dynamic::object("type", "value")("value", 0)("offset", 0));
nodesManager_->createAnimatedNode(
gTag, folly::dynamic::object("type", "value")("value", 255)("offset", 0));
nodesManager_->createAnimatedNode(
bTag, folly::dynamic::object("type", "value")("value", 0)("offset", 0));
nodesManager_->createAnimatedNode(
aTag, folly::dynamic::object("type", "value")("value", 0.5)("offset", 0));
nodesManager_->createAnimatedNode(
colorNodeTag,
folly::dynamic::object("type", "color")("r", rTag)("g", gTag)("b", bTag)(
"a", aTag));
nodesManager_->connectAnimatedNodes(rTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(gTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(bTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(aTag, colorNodeTag);
// Create opacity ValueNode
auto opacityNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
opacityNodeTag,
folly::dynamic::object("type", "value")("value", 0.8f)("offset", 0));
// Create StyleNode
auto styleNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
styleNodeTag,
folly::dynamic::object("type", "style")(
"style",
folly::dynamic::object("backgroundColor", colorNodeTag)(
"opacity", opacityNodeTag)));
nodesManager_->connectAnimatedNodes(colorNodeTag, styleNodeTag);
nodesManager_->connectAnimatedNodes(opacityNodeTag, styleNodeTag);
// Create PropsNode
auto propsNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
propsNodeTag,
folly::dynamic::object("type", "props")(
"props", folly::dynamic::object("style", styleNodeTag)));
nodesManager_->connectAnimatedNodes(styleNodeTag, propsNodeTag);
// Connect PropsNode to View
auto viewTag = ++rootTag;
nodesManager_->connectAnimatedNodeToView(propsNodeTag, viewTag);
runAnimationFrame(0);
// Step 2: Update backgroundColor
{
nodesManager_->setAnimatedNodeValue(bTag, 100);
nodesManager_->setAnimatedNodeValue(aTag, 0.3);
// Confirm the nodes graph is correctly marked dirty
EXPECT_EQ(nodeNeedsUpdate(rTag), false);
EXPECT_EQ(nodeNeedsUpdate(gTag), false);
EXPECT_EQ(nodeNeedsUpdate(bTag), true);
EXPECT_EQ(nodeNeedsUpdate(aTag), true);
EXPECT_EQ(nodeNeedsUpdate(opacityNodeTag), false);
// connected style/prop nodes are not marked dirty but they will be updated
// at next render
EXPECT_EQ(nodeNeedsUpdate(styleNodeTag), false);
EXPECT_EQ(nodeNeedsUpdate(propsNodeTag), false);
// Flush changes
runAnimationFrame(0);
// Check props commit done via MountingManager
auto color =
static_cast<Color>(lastCommittedProps["backgroundColor"].asInt());
EXPECT_EQ(redFromColor({color}), 0);
EXPECT_EQ(greenFromColor({color}), 255);
EXPECT_EQ(blueFromColor({color}), 100);
EXPECT_EQ(alphaFromColor({color}), static_cast<uint8_t>(0.3 * 255));
EXPECT_EQ(lastUpdatedNodeTag, viewTag);
}
// Step 3: Update opacity
{
nodesManager_->setAnimatedNodeValue(opacityNodeTag, 0.1f);
// Confirm the nodes graph is correctly marked dirty
EXPECT_EQ(nodeNeedsUpdate(rTag), false);
EXPECT_EQ(nodeNeedsUpdate(gTag), false);
EXPECT_EQ(nodeNeedsUpdate(bTag), false);
EXPECT_EQ(nodeNeedsUpdate(aTag), false);
EXPECT_EQ(nodeNeedsUpdate(opacityNodeTag), true);
// connected style/prop nodes are not marked dirty but they will be updated
// at next render
EXPECT_EQ(nodeNeedsUpdate(styleNodeTag), false);
EXPECT_EQ(nodeNeedsUpdate(propsNodeTag), false);
// Flush changes
runAnimationFrame(0);
// Check props commit done via MountingManager
EXPECT_EQ(lastCommittedProps["opacity"], 0.1f);
EXPECT_EQ(lastUpdatedNodeTag, viewTag);
}
}
TEST_F(AnimatedNodeTests, ModulusAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto moduloTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 1));
nodesManager_->createAnimatedNode(
moduloTag,
folly::dynamic::object("type", "modulus")("input", valueTag)(
"modulus", 3.1));
nodesManager_->connectAnimatedNodes(valueTag, moduloTag);
runAnimationFrame(0);
nodesManager_->setAnimatedNodeValue(valueTag, 4.1);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(valueTag), 5.1);
EXPECT_EQ(nodesManager_->getValue(moduloTag), std::fmod(5.1, 3.1));
nodesManager_->setAnimatedNodeValue(valueTag, 7.6);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(valueTag), 8.6);
EXPECT_EQ(nodesManager_->getValue(moduloTag), std::fmod(8.6, 3.1));
}
TEST_F(AnimatedNodeTests, DiffClampAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto diffClampTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 4)("offset", 0));
nodesManager_->createAnimatedNode(
diffClampTag,
folly::dynamic::object("type", "diffclamp")("input", valueTag)("min", 1)(
"max", 2));
nodesManager_->connectAnimatedNodes(valueTag, diffClampTag);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(diffClampTag), 2);
nodesManager_->setAnimatedNodeValue(valueTag, 2);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(diffClampTag), 1);
}
TEST_F(AnimatedNodeTests, ObjectAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto objectTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 4)("offset", 0));
nodesManager_->createAnimatedNode(
objectTag,
folly::dynamic::object("type", "object")(
"value",
folly::dynamic::array(
folly::dynamic::object(
"translate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)),
folly::dynamic::object(
"rotate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)(
"angle", "180deg")),
folly::dynamic::object(
"scale3d", folly::dynamic::object("nodeTag", valueTag)))));
nodesManager_->connectAnimatedNodes(valueTag, objectTag);
const auto objectNode =
nodesManager_->getAnimatedNode<ObjectAnimatedNode>(objectTag);
folly::dynamic collectedProps = folly::dynamic::object();
objectNode->collectViewUpdates("test", collectedProps);
const auto expected = folly::dynamic::object(
"test",
folly::dynamic::array(
folly::dynamic::object(
"translate3d", folly::dynamic::object("x", 1)("y", 0)("z", 0)),
folly::dynamic::object(
"rotate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)(
"angle", "180deg")),
folly::dynamic::object("scale3d", 4)));
EXPECT_EQ(collectedProps["test"].size(), 3);
EXPECT_EQ(collectedProps["test"][0]["translate3d"]["x"], 1);
EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["y"], 0);
EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["angle"], "180deg");
EXPECT_EQ(collectedProps["test"][2]["scale3d"], 4);
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* 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 "AnimationTestsBase.h"
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
namespace facebook::react {
class AnimationDriverTests : public AnimationTestsBase {
protected:
double round(double value) noexcept {
// Round to 2 decimal places
return std::ceil(value * 100) / 100;
}
};
TEST_F(AnimationDriverTests, framesAnimation) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueNodeTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 0));
const auto animationId = 1;
const auto frames = folly::dynamic::array(0.0f, 0.1f, 0.4f, 0.9f, 1.0f);
const auto toValue = 100;
nodesManager_->startAnimatingNode(
animationId,
valueNodeTag,
folly::dynamic::object("type", "frames")("frames", frames)(
"toValue", toValue),
std::nullopt);
const double startTimeInTick = 12345;
runAnimationFrame(startTimeInTick);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 0);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 2.5);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 65);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 3);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 90);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 4);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 10);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue);
}
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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
#ifdef _WIN32
#include <folly/portability/Unistd.h>
#include <folly/portability/Windows.h>
#endif
#include <gtest/gtest.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
class AnimationTestsBase : public testing::Test {
public:
AnimationTestsBase() = default;
protected:
void initNodesManager() noexcept
{
nodesManager_.reset();
nodesManager_ = std::make_shared<NativeAnimatedNodesManager>(
[this](Tag reactTag, const folly::dynamic &changedProps) {
lastUpdatedNodeTag = reactTag;
lastCommittedProps = changedProps;
},
[this](const std::unordered_map<Tag, folly::dynamic> &nodesProps) {
if (!nodesProps.empty()) {
lastUpdatedNodeTag = nodesProps.begin()->first;
lastCommittedProps = nodesProps.begin()->second;
}
});
NativeAnimatedNodesManager::isOnRenderThread_ = true;
}
bool nodeNeedsUpdate(Tag nodeTag) const
{
return nodesManager_->updatedNodeTags_.contains(nodeTag);
}
void runAnimationFrame(double timestamp)
{
nodesManager_->onAnimationFrame(timestamp);
}
std::shared_ptr<NativeAnimatedNodesManager> nodesManager_;
folly::dynamic lastCommittedProps{folly::dynamic::object()};
Tag lastUpdatedNodeTag{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,70 @@
/*
* 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 "AnimationTestsBase.h"
#include <react/renderer/components/scrollview/ScrollEvent.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
namespace facebook::react {
class EventAnimationDriverTests : public AnimationTestsBase {};
TEST_F(EventAnimationDriverTests, subscribeToViewEvent) {
initNodesManager();
auto tag = getNextRootViewTag();
auto viewTag = ++tag;
const auto animatedValueTag = ++tag;
const auto animatedValueTag2 = ++tag;
const auto eventName = "scroll";
const folly::dynamic valueNodeConfig =
folly::dynamic::object("type", "value")("value", 0)("offset", 0);
// Call onRender once to initialize thread local
nodesManager_->onRender();
{
nodesManager_->createAnimatedNode(animatedValueTag, valueNodeConfig);
folly::dynamic eventMapping =
folly::dynamic::object("animatedValueTag", animatedValueTag)(
"nativeEventPath", folly::dynamic::array("contentOffset", "y"));
nodesManager_->addAnimatedEventToView(viewTag, eventName, eventMapping);
}
{
nodesManager_->createAnimatedNode(animatedValueTag2, valueNodeConfig);
folly::dynamic eventMapping =
folly::dynamic::object("animatedValueTag", animatedValueTag2)(
"nativeEventPath", folly::dynamic::array("zoomScale"));
nodesManager_->addAnimatedEventToView(viewTag, eventName, eventMapping);
}
EXPECT_EQ(nodesManager_->getValue(animatedValueTag), 0);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag2), 0);
auto scrollEvent = std::make_shared<ScrollEvent>();
scrollEvent->contentSize = {.width = 1, .height = 2};
scrollEvent->contentOffset = {.x = 3, .y = 4};
scrollEvent->contentInset = {.left = 5, .top = 6, .right = 7, .bottom = 8};
scrollEvent->containerSize = {.width = 9, .height = 10};
scrollEvent->zoomScale = 11.0f;
const std::string eventType{eventName};
const SharedEventPayload payload = scrollEvent;
(*nodesManager_->getEventEmitterListener())(viewTag, eventName, *scrollEvent);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag), 4);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag2), 11);
}
} // namespace facebook::react

View File

@@ -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

View File

@@ -0,0 +1,52 @@
/*
* 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

View File

@@ -0,0 +1,176 @@
/*
* 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 "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,
FabricCommitCallback&& fabricCommitCallback,
UIManager* uiManager)
: startOnRenderCallback_(std::move(startOnRenderCallback)),
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
directManipulationCallback_(std::move(directManipulationCallback)),
fabricCommitCallback_(std::move(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(static_cast<float>(timestamp));
for (auto& mutation : muatations) {
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, bool isAsync) {
callbacks.push_back(callback);
// TODO: startOnRenderCallback_ should provide the timestamp from the platform
startOnRenderCallback_(
[this]() {
onAnimationFrame(
std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
},
isAsync);
}
void AnimationBackend::stop(bool isAsync) {
stopOnRenderCallback_(isAsync);
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

View File

@@ -0,0 +1,59 @@
/*
* 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 <folly/dynamic.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
#include <functional>
#include <vector>
#include "AnimatedProps.h"
#include "AnimatedPropsBuilder.h"
namespace facebook::react {
struct AnimationMutation {
Tag tag;
const ShadowNodeFamily *family;
AnimatedProps props;
};
using AnimationMutations = std::vector<AnimationMutation>;
class AnimationBackend : public UIManagerAnimationBackend {
public:
using Callback = std::function<AnimationMutations(float)>;
using StartOnRenderCallback = std::function<void(std::function<void()> &&, bool /* isAsync */)>;
using StopOnRenderCallback = std::function<void(bool /* isAsync */)>;
using DirectManipulationCallback = std::function<void(Tag, const folly::dynamic &)>;
using FabricCommitCallback = std::function<void(std::unordered_map<Tag, folly::dynamic> &)>;
std::vector<Callback> callbacks;
const StartOnRenderCallback startOnRenderCallback_;
const StopOnRenderCallback stopOnRenderCallback_;
const DirectManipulationCallback directManipulationCallback_;
const FabricCommitCallback fabricCommitCallback_;
UIManager *uiManager_;
AnimationBackend(
StartOnRenderCallback &&startOnRenderCallback,
StopOnRenderCallback &&stopOnRenderCallback,
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) override;
void start(const Callback &callback, bool isAsync);
void stop(bool isAsync) override;
};
} // namespace facebook::react

View File

@@ -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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_animationbackend_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_animationbackend OBJECT ${react_renderer_animationbackend_SRC})
target_include_directories(react_renderer_animationbackend PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_animationbackend
react_codegen_rncore
react_debug
react_renderer_core
react_renderer_graphics
react_renderer_mounting
react_renderer_uimanager
react_renderer_scheduler
glog
folly_runtime
)
target_compile_reactnative_options(react_renderer_animationbackend PRIVATE)
target_compile_options(react_renderer_animationbackend PRIVATE -Wpedantic)

View File

@@ -0,0 +1,33 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_animations_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_animations STATIC ${react_renderer_animations_SRC})
target_include_directories(react_renderer_animations PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_animations
folly_runtime
glog
glog_init
jsi
react_debug
react_renderer_componentregistry
react_renderer_core
react_renderer_debug
react_renderer_graphics
react_renderer_mounting
react_renderer_uimanager
rrc_view
runtimeexecutor
yoga
)
target_compile_reactnative_options(react_renderer_animations PRIVATE)
target_compile_options(react_renderer_animations PRIVATE -Wpedantic)

View File

@@ -0,0 +1,35 @@
/*
* 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 <jsi/jsi.h>
#include <memory>
namespace facebook::react {
class LayoutAnimationCallbackWrapper {
public:
LayoutAnimationCallbackWrapper(jsi::Function &&callback)
: callback_(std::make_shared<jsi::Function>(std::move(callback)))
{
}
LayoutAnimationCallbackWrapper() : callback_(nullptr) {}
void call(jsi::Runtime &runtime) const
{
if (callback_) {
callback_->call(runtime);
callback_.reset();
}
}
private:
mutable std::shared_ptr<jsi::Function> callback_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,110 @@
/*
* 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 "LayoutAnimationDriver.h"
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/animations/utils.h>
#include <algorithm>
namespace facebook::react {
void LayoutAnimationDriver::animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const {
for (auto& animation : inflightAnimations_) {
if (animation.surfaceId != surfaceId) {
continue;
}
if (animation.completed) {
continue;
}
int incompleteAnimations = 0;
for (auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
const auto& baselineShadowView = keyframe.viewStart;
const auto& finalShadowView = keyframe.viewEnd;
// The contract with the "keyframes generation" phase is that any animated
// node will have a valid configuration.
const auto layoutAnimationConfig = animation.layoutAnimationConfig;
const auto& mutationConfig =
(keyframe.type == AnimationConfigurationType::Delete
? layoutAnimationConfig.deleteConfig
: (keyframe.type == AnimationConfigurationType::Create
? layoutAnimationConfig.createConfig
: layoutAnimationConfig.updateConfig));
// Interpolate
auto progress =
calculateAnimationProgress(now, animation, mutationConfig);
auto animationTimeProgressLinear = progress.first;
auto animationInterpolationFactor = progress.second;
auto mutatedShadowView = createInterpolatedShadowView(
animationInterpolationFactor, baselineShadowView, finalShadowView);
// Create the mutation instruction
mutationsList.emplace_back(
ShadowViewMutation::UpdateMutation(
keyframe.viewPrev, mutatedShadowView, keyframe.parentTag));
PrintMutationInstruction("Animation Progress:", mutationsList.back());
keyframe.viewPrev = std::move(mutatedShadowView);
if (animationTimeProgressLinear < 1) {
incompleteAnimations++;
}
}
// Are there no ongoing mutations left in this animation?
if (incompleteAnimations == 0) {
animation.completed = true;
}
}
// Clear out finished animations
for (auto it = inflightAnimations_.begin();
it != inflightAnimations_.end();) {
const auto& animation = *it;
if (animation.completed) {
callCallback(animation.successCallback);
// Queue up "final" mutations for all keyframes in the completed animation
for (const auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
queueFinalMutationsForCompletedKeyFrame(
keyframe,
mutationsList,
false,
"LayoutAnimationDriver: Animation Completed");
}
it = inflightAnimations_.erase(it);
} else {
it++;
}
}
// Final step: make sure that all operations execute in the proper order.
// REMOVE operations with highest indices must operate first.
std::stable_sort(
mutationsList.begin(),
mutationsList.end(),
&shouldFirstComeBeforeSecondMutation);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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/animations/LayoutAnimationKeyFrameManager.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
namespace facebook::react {
class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager {
public:
LayoutAnimationDriver(
RuntimeExecutor runtimeExecutor,
std::shared_ptr<const ContextContainer> &contextContainer,
LayoutAnimationStatusDelegate *delegate)
: LayoutAnimationKeyFrameManager(runtimeExecutor, contextContainer, delegate)
{
}
protected:
virtual void animationMutationsForFrame(SurfaceId surfaceId, ShadowViewMutation::List &mutationsList, uint64_t now)
const override;
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/animations/LayoutAnimationCallbackWrapper.h>
#include <react/renderer/animations/primitives.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/debug/flags.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/uimanager/LayoutAnimationStatusDelegate.h>
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <optional>
#include <unordered_set>
namespace facebook::react {
#ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
void PrintMutationInstruction(std::string message, const ShadowViewMutation &mutation);
void PrintMutationInstructionRelative(
std::string message,
const ShadowViewMutation &mutation,
const ShadowViewMutation &relativeMutation);
#else
#define PrintMutationInstruction(a, b)
#define PrintMutationInstructionRelative(a, b, c)
#endif
class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, public MountingOverrideDelegate {
public:
LayoutAnimationKeyFrameManager(
RuntimeExecutor runtimeExecutor,
std::shared_ptr<const ContextContainer> &contextContainer,
LayoutAnimationStatusDelegate *delegate);
#pragma mark - UIManagerAnimationDelegate methods
void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime &runtime,
const RawValue &config,
const jsi::Value &successCallbackValue,
const jsi::Value &failureCallbackValue) const override;
void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry) override;
// TODO: add SurfaceId to this API as well
bool shouldAnimateFrame() const override;
void stopSurface(SurfaceId surfaceId) override;
#pragma mark - MountingOverrideDelegate methods
bool shouldOverridePullTransaction() const override;
// This is used to "hijack" the diffing process to figure out which mutations
// should be animated. The mutations returned by this function will be
// executed immediately.
std::optional<MountingTransaction> pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number number,
const TransactionTelemetry &telemetry,
ShadowViewMutationList mutations) const override;
// Exposed for testing.
void uiManagerDidConfigureNextLayoutAnimation(LayoutAnimation layoutAnimation) const;
// LayoutAnimationStatusDelegate - this is for the platform to get
// signal when animations start and complete. Setting and resetting this
// delegate is protected by a mutex; ALL method calls into this delegate are
// also protected by the mutex! The only way to set this without a mutex is
// via a constructor.
void setLayoutAnimationStatusDelegate(LayoutAnimationStatusDelegate *delegate) const;
void setClockNow(std::function<uint64_t()> now);
protected:
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
mutable std::optional<LayoutAnimation> currentAnimation_{};
mutable std::mutex currentAnimationMutex_;
/**
* All mutations of inflightAnimations_ are thread-safe as long as
* we keep the contract of: only mutate it within the context of
* `pullTransaction`. If that contract is held, this is implicitly protected
* by the MountingCoordinator's mutex.
*/
mutable std::vector<LayoutAnimation> inflightAnimations_{};
bool hasComponentDescriptorForShadowView(const ShadowView &shadowView) const;
const ComponentDescriptor &getComponentDescriptorForShadowView(const ShadowView &shadowView) const;
/**
* Given a `progress` between 0 and 1, a mutation and LayoutAnimation config,
* return a ShadowView with mutated props and/or LayoutMetrics.
*
* @param progress the current progress for the animation
* @param startingView the initial configuration of the ShadowView
* @param finalView the final configuration of the ShadowView
* @return the current ShadowView
*/
ShadowView createInterpolatedShadowView(Float progress, const ShadowView &startingView, const ShadowView &finalView)
const;
void callCallback(const LayoutAnimationCallbackWrapper &callback) const;
virtual void animationMutationsForFrame(SurfaceId surfaceId, ShadowViewMutation::List &mutationsList, uint64_t now)
const = 0;
/**
* Queue (and potentially synthesize) final mutations for a finished keyframe.
* Keyframe animation may have timed-out, or be canceled due to a conflict.
*/
void queueFinalMutationsForCompletedKeyFrame(
const AnimationKeyFrame &keyframe,
ShadowViewMutation::List &mutationsList,
bool interrupted,
const std::string &logPrefix) const;
private:
RuntimeExecutor runtimeExecutor_;
std::shared_ptr<const ContextContainer> contextContainer_;
mutable std::mutex layoutAnimationStatusDelegateMutex_;
mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{};
mutable std::mutex surfaceIdsToStopMutex_;
mutable std::unordered_set<SurfaceId> surfaceIdsToStop_{};
// Function that returns current time in milliseconds
std::function<uint64_t()> now_;
void adjustImmediateMutationIndicesForDelayedMutations(
SurfaceId surfaceId,
ShadowViewMutation &mutation,
bool skipLastAnimation = false,
bool lastAnimationOnly = false) const;
void adjustDelayedMutationIndicesForMutation(
SurfaceId surfaceId,
const ShadowViewMutation &mutation,
bool skipLastAnimation = false) const;
void getAndEraseConflictingAnimations(
SurfaceId surfaceId,
const ShadowViewMutationList &mutations,
std::vector<AnimationKeyFrame> &conflictingAnimations) const;
/*
* Removes animations from `inflightAnimations_` for stopped surfaces.
*/
void deleteAnimationsForStoppedSurfaces() const;
void simulateImagePropsMemoryAccess(const ShadowViewMutationList &mutations) const;
/**
* Interpolates the props values.
*/
Props::Shared interpolateProps(
const ComponentDescriptor &componentDescriptor,
const PropsParserContext &context,
Float animationProgress,
const Props::Shared &props,
const Props::Shared &newProps,
const Size &size) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,203 @@
/*
* 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 <glog/logging.h>
#include <react/renderer/animations/primitives.h>
#include <optional>
namespace facebook::react {
static inline std::optional<AnimationType> parseAnimationType(std::string param)
{
if (param == "spring") {
return AnimationType::Spring;
}
if (param == "linear") {
return AnimationType::Linear;
}
if (param == "easeInEaseOut") {
return AnimationType::EaseInEaseOut;
}
if (param == "easeIn") {
return AnimationType::EaseIn;
}
if (param == "easeOut") {
return AnimationType::EaseOut;
}
if (param == "keyboard") {
return AnimationType::Keyboard;
}
LOG(ERROR) << "Error parsing animation type: " << param;
return {};
}
static inline std::optional<AnimationProperty> parseAnimationProperty(std::string param)
{
if (param == "opacity") {
return AnimationProperty::Opacity;
}
if (param == "scaleX") {
return AnimationProperty::ScaleX;
}
if (param == "scaleY") {
return AnimationProperty::ScaleY;
}
if (param == "scaleXY") {
return AnimationProperty::ScaleXY;
}
LOG(ERROR) << "Error parsing animation property: " << param;
return {};
}
static inline std::optional<AnimationConfig>
parseAnimationConfig(const folly::dynamic &config, double defaultDuration, bool parsePropertyType)
{
if (config.empty() || !config.isObject()) {
return AnimationConfig{
.animationType = AnimationType::Linear,
.animationProperty = AnimationProperty::NotApplicable,
.duration = defaultDuration,
.delay = 0,
.springDamping = 0,
.initialVelocity = 0};
}
const auto typeIt = config.find("type");
if (typeIt == config.items().end()) {
LOG(ERROR) << "Error parsing animation config: could not find field `type`";
return {};
}
const auto animationTypeParam = typeIt->second;
if (animationTypeParam.empty() || !animationTypeParam.isString()) {
LOG(ERROR) << "Error parsing animation config: could not unwrap field `type`";
return {};
}
const auto animationType = parseAnimationType(animationTypeParam.asString());
if (!animationType) {
LOG(ERROR) << "Error parsing animation config: could not parse field `type`";
return {};
}
AnimationProperty animationProperty = AnimationProperty::NotApplicable;
if (parsePropertyType) {
const auto propertyIt = config.find("property");
if (propertyIt == config.items().end()) {
LOG(ERROR) << "Error parsing animation config: could not find field `property`";
return {};
}
const auto animationPropertyParam = propertyIt->second;
if (animationPropertyParam.empty() || !animationPropertyParam.isString()) {
LOG(ERROR) << "Error parsing animation config: could not unwrap field `property`";
return {};
}
const auto animationPropertyParsed = parseAnimationProperty(animationPropertyParam.asString());
if (!animationPropertyParsed) {
LOG(ERROR) << "Error parsing animation config: could not parse field `property`";
return {};
}
animationProperty = *animationPropertyParsed;
}
double duration = defaultDuration;
double delay = 0;
Float springDamping = 0.5;
Float initialVelocity = 0;
const auto durationIt = config.find("duration");
if (durationIt != config.items().end()) {
if (durationIt->second.isDouble()) {
duration = durationIt->second.asDouble();
} else {
LOG(ERROR) << "Error parsing animation config: field `duration` must be a number";
return {};
}
}
const auto delayIt = config.find("delay");
if (delayIt != config.items().end()) {
if (delayIt->second.isDouble()) {
delay = delayIt->second.asDouble();
} else {
LOG(ERROR) << "Error parsing animation config: field `delay` must be a number";
return {};
}
}
const auto springDampingIt = config.find("springDamping");
if (springDampingIt != config.items().end() && springDampingIt->second.isDouble()) {
if (springDampingIt->second.isDouble()) {
springDamping = (Float)springDampingIt->second.asDouble();
} else {
LOG(ERROR) << "Error parsing animation config: field `springDamping` must be a number";
return {};
}
}
const auto initialVelocityIt = config.find("initialVelocity");
if (initialVelocityIt != config.items().end()) {
if (initialVelocityIt->second.isDouble()) {
initialVelocity = (Float)initialVelocityIt->second.asDouble();
} else {
LOG(ERROR) << "Error parsing animation config: field `initialVelocity` must be a number";
return {};
}
}
return std::optional<AnimationConfig>(AnimationConfig{
.animationType = *animationType,
.animationProperty = animationProperty,
.duration = duration,
.delay = delay,
.springDamping = springDamping,
.initialVelocity = initialVelocity});
}
// Parse animation config from JS
static inline std::optional<LayoutAnimationConfig> parseLayoutAnimationConfig(const folly::dynamic &config)
{
if (config.empty() || !config.isObject()) {
return {};
}
const auto durationIt = config.find("duration");
if (durationIt == config.items().end() || !durationIt->second.isDouble()) {
return {};
}
const double duration = durationIt->second.asDouble();
const auto createConfigIt = config.find("create");
const auto createConfig = createConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(createConfigIt->second, duration, true);
const auto updateConfigIt = config.find("update");
const auto updateConfig = updateConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(updateConfigIt->second, duration, false);
const auto deleteConfigIt = config.find("delete");
const auto deleteConfig = deleteConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(deleteConfigIt->second, duration, true);
if (!createConfig || !updateConfig || !deleteConfig) {
return {};
}
return LayoutAnimationConfig{
.duration = duration,
.createConfig = *createConfig,
.updateConfig = *updateConfig,
.deleteConfig = *deleteConfig};
}
} // namespace facebook::react

View File

@@ -0,0 +1,96 @@
/*
* 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/animations/LayoutAnimationCallbackWrapper.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/mounting/ShadowView.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <vector>
namespace facebook::react {
// This corresponds exactly with JS.
enum class AnimationType {
None = 0,
Spring = 1,
Linear = 2,
EaseInEaseOut = 4,
EaseIn = 8,
EaseOut = 16,
Keyboard = 32
};
enum class AnimationProperty { NotApplicable = 0, Opacity = 1, ScaleX = 2, ScaleY = 4, ScaleXY = 8 };
// This corresponds exactly with JS.
struct AnimationConfig {
AnimationType animationType = AnimationType::None;
AnimationProperty animationProperty = AnimationProperty::NotApplicable;
double duration = 0; // these are perhaps better represented as uint64_t, but they
// come from JS as doubles
double delay = 0;
Float springDamping = 0;
Float initialVelocity = 0;
};
// This corresponds exactly with JS.
struct LayoutAnimationConfig {
double duration; // ms
AnimationConfig createConfig;
AnimationConfig updateConfig;
AnimationConfig deleteConfig;
};
enum class AnimationConfigurationType { Create = 1, Update = 2, Delete = 4 };
struct AnimationKeyFrame {
// The mutation(s) that should be executed once the animation completes.
// This maybe empty.
// For CREATE/INSERT this will contain CREATE, INSERT in that order.
// For REMOVE/DELETE, same.
std::vector<ShadowViewMutation> finalMutationsForKeyFrame;
// The type of animation this is (for configuration purposes)
AnimationConfigurationType type;
// Tag representing the node being animated.
Tag tag;
Tag parentTag;
// ShadowView representing the start and end points of this animation.
ShadowView viewStart;
ShadowView viewEnd;
// ShadowView representing the previous frame of the animation.
ShadowView viewPrev;
// If an animation interrupts an existing one, the starting state may actually
// be halfway through the intended transition.
double initialProgress;
bool invalidated{false};
// In the case where some mutation conflicts with this keyframe,
// should we generate final synthetic UPDATE mutations for this keyframe?
bool generateFinalSyntheticMutations{true};
};
struct LayoutAnimation {
SurfaceId surfaceId;
uint64_t startTime;
bool completed = false;
LayoutAnimationConfig layoutAnimationConfig;
LayoutAnimationCallbackWrapper successCallback;
LayoutAnimationCallbackWrapper failureCallback;
std::vector<AnimationKeyFrame> keyFrames;
};
} // namespace facebook::react

View File

@@ -0,0 +1,557 @@
/*
* 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 <vector>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/animations/LayoutAnimationDriver.h>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs/stubs.h>
#include <react/test_utils/Entropy.h>
#include <react/test_utils/MockClock.h>
#include <react/test_utils/shadowTreeGeneration.h>
// Uncomment when random test blocks are uncommented below.
// #include <algorithm>
// #include <random>
MockClock::time_point MockClock::time_ = {};
namespace facebook::react {
static void testShadowNodeTreeLifeCycleLayoutAnimations(
uint_fast32_t seed,
int treeSize,
int repeats,
int stages,
int animation_duration,
int animation_frames,
int delay_ms_between_frames,
int delay_ms_between_stages,
int delay_ms_between_repeats,
bool commits_conflicting_mutations = false,
int final_animation_delay = 0) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<const ContextContainer>();
auto componentDescriptorParameters = ComponentDescriptorParameters{
.eventDispatcher = eventDispatcher,
.contextContainer = contextContainer,
.flavor = nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
PropsParserContext parserContext{-1, *contextContainer};
// Create a RuntimeExecutor
RuntimeExecutor runtimeExecutor =
[](const std::function<void(jsi::Runtime&)>& /*unused*/) {};
// Create component descriptor registry for animation driver
auto providerRegistry =
std::make_shared<ComponentDescriptorProviderRegistry>();
auto componentDescriptorRegistry =
providerRegistry->createComponentDescriptorRegistry(
componentDescriptorParameters);
providerRegistry->add(
concreteComponentDescriptorProvider<ViewComponentDescriptor>());
// Create Animation Driver
auto animationDriver = std::make_shared<LayoutAnimationDriver>(
runtimeExecutor, contextContainer, nullptr);
animationDriver->setComponentDescriptorRegistry(componentDescriptorRegistry);
// Mock animation timers
animationDriver->setClockNow([]() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
MockClock::now().time_since_epoch())
.count();
});
auto allNodes = std::vector<std::shared_ptr<const ShadowNode>>{};
for (int i = 0; i < repeats; i++) {
allNodes.clear();
int surfaceIdInt = 1;
auto surfaceId = SurfaceId(surfaceIdInt);
auto family = rootComponentDescriptor.createFamily(
{.tag = Tag(surfaceIdInt),
.surfaceId = surfaceId,
.instanceHandle = nullptr});
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<const RootShadowNode>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{
.props = RootShadowNode::defaultSharedProps()},
family)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
parserContext,
LayoutConstraints{
.minimumSize = Size{.width = 512, .height = 0},
.maximumSize =
Size{
.width = 512,
.height = std::numeric_limits<Float>::infinity()}},
LayoutContext{});
// Generation of a random tree.
auto singleRootChildNode =
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
// Injecting a tree into the root node.
auto currentRootNode = std::static_pointer_cast<const RootShadowNode>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = std::make_shared<
std::vector<std::shared_ptr<const ShadowNode>>>(
std::vector<std::shared_ptr<const ShadowNode>>{
singleRootChildNode})}));
// Building an initial view hierarchy.
auto viewTree = StubViewTree(ShadowView(*emptyRootNode));
viewTree.mutate(
calculateShadowViewMutations(*emptyRootNode, *currentRootNode));
for (int j = 0; j < stages; j++) {
auto nextRootNode = currentRootNode;
// Mutating the tree.
alterShadowTree(
entropy,
nextRootNode,
{
&messWithChildren,
&messWithYogaStyles,
&messWithLayoutableOnlyFlag,
});
std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);
nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);
// Calculating mutations.
auto originalMutations =
calculateShadowViewMutations(*currentRootNode, *nextRootNode);
// If tree randomization produced no changes in the form of mutations,
// don't bother trying to animate because this violates a bunch of our
// assumptions in this test
if (originalMutations.empty()) {
continue;
}
// If we only mutated the root... also don't bother
if (originalMutations.size() == 1 &&
(originalMutations[0].oldChildShadowView.tag == 1 ||
originalMutations[0].newChildShadowView.tag == 1)) {
continue;
}
// Configure animation
animationDriver->uiManagerDidConfigureNextLayoutAnimation(
{.surfaceId = surfaceId,
.startTime = 0,
.completed = false,
.layoutAnimationConfig =
{.duration = (double)animation_duration,
.createConfig = {
/* Create */ .animationType = AnimationType::EaseInEaseOut,
.animationProperty = AnimationProperty::Opacity,
.duration = (double)animation_duration,
.delay = 0,
.springDamping = 0,
.initialVelocity = 0},
.updateConfig = {
/* Update */ .animationType = AnimationType::EaseInEaseOut,
.animationProperty = AnimationProperty::ScaleXY,
.duration = (double)animation_duration,
.delay = 0,
.springDamping = 0,
.initialVelocity = 0},
.deleteConfig = {
/* Delete */ .animationType = AnimationType::EaseInEaseOut,
.animationProperty = AnimationProperty::Opacity,
.duration = (double)animation_duration,
.delay = 0,
.springDamping = 0,
.initialVelocity = 0}},
.successCallback = {},
.failureCallback = {},
.keyFrames = {}});
// Get mutations for each frame
for (int k = 0; k < animation_frames + 2; k++) {
auto mutationsInput = ShadowViewMutation::List{};
if (k == 0) {
mutationsInput = originalMutations;
}
if (k != (animation_frames + 1)) {
EXPECT_TRUE(animationDriver->shouldOverridePullTransaction());
} else if (!commits_conflicting_mutations) {
EXPECT_FALSE(animationDriver->shouldOverridePullTransaction());
}
auto telemetry = TransactionTelemetry{};
telemetry.willLayout();
telemetry.willCommit();
telemetry.willDiff();
auto transaction = animationDriver->pullTransaction(
surfaceId, 0, telemetry, mutationsInput);
EXPECT_TRUE(transaction.has_value() || k == animation_frames);
// We have something to validate.
if (transaction.has_value()) {
auto mutations = transaction->getMutations();
// Mutating the view tree.
viewTree.mutate(mutations);
// We don't do any validation on this until all animations are
// finished!
}
MockClock::advance_by(
std::chrono::milliseconds(delay_ms_between_frames));
}
// After the animation is completed...
// Build a view tree to compare with.
// After all the synthetic mutations, at the end of the animation,
// the mutated and newly-constructed trees should be identical.
if (!commits_conflicting_mutations) {
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR)
<< "Entropy seed: " << entropy.getSeed()
<< ". To see why trees are different, define STUB_VIEW_TREE_VERBOSE and see logging in StubViewTree.cpp.\n";
EXPECT_TRUE(false);
}
}
currentRootNode = nextRootNode;
MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_stages));
}
// Flush all remaining animations before validating trees
if (final_animation_delay > 0) {
MockClock::advance_by(std::chrono::milliseconds(final_animation_delay));
auto telemetry = TransactionTelemetry{};
telemetry.willLayout();
telemetry.willCommit();
telemetry.willDiff();
auto transaction =
animationDriver->pullTransaction(surfaceId, 0, telemetry, {});
// We have something to validate.
if (transaction.has_value()) {
auto mutations = transaction->getMutations();
// Mutating the view tree.
viewTree.mutate(mutations);
// We don't do any validation on this until all animations are
// finished!
}
}
// After all animations are completed...
// Build a view tree to compare with.
// After all the synthetic mutations, at the end of the animation,
// the mutated and newly-constructed trees should be identical.
if (commits_conflicting_mutations) {
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*currentRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR)
<< "Entropy seed: " << entropy.getSeed()
<< ". To see why trees are different, define STUB_VIEW_TREE_VERBOSE and see logging in StubViewTree.cpp.\n";
EXPECT_TRUE(false);
}
}
MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_repeats));
}
SUCCEED();
}
} // namespace facebook::react
using namespace facebook::react;
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_2029343357) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357, /* working seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_3619914559) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 3619914559, /* working seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_597132284) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_774986518) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 774986518, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_1450614414) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 1450614414, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsFewStages_NonOverlapping) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsManyStages_NonOverlapping) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 128,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
// You may uncomment this - locally only! - to generate failing seeds.
// TEST(LayoutAnimationTest, stableSmallerTreeFewRepeatsFewStages_Random) {
// std::random_device device;
// for (int i = 0; i < 10; i++) {
// uint_fast32_t seed = device();
// LOG(ERROR) << "Seed: " << seed;
// testShadowNodeTreeLifeCycleLayoutAnimations(
// /* seed */ seed,
// /* size */ 128,
// /* repeats */ 128,
// /* stages */ 10,
// /* animation_duration */ 1000,
// /* animation_frames*/ 10,
// /* delay_ms_between_frames */ 100,
// /* delay_ms_between_stages */ 100,
// /* delay_ms_between_repeats */ 2000);
// }
// // Fail if you want output to get seeds
// LOG(ERROR) << "ALL RUNS SUCCESSFUL";
// // react_native_assert(false);
// }
//
// These tests are "overlapping", meaning that mutations will be committed
// before the previous animation completes.
//
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_2029343357) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 9, // an animation completes in 10 frames, so this
// causes conflicts
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 10000 + 1);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_597132284) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284,
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 9, // an animation completes in 10 frames, so this
// causes conflicts
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 10000 + 1);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_ManyConflicts_597132284) {
GTEST_SKIP();
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284,
/* size */ 128,
/* repeats */ 128,
/* stages */ 50,
/* animation_duration */ 1000,
/* animation_frames*/ 5, // an animation completes in 10 frames, so this
// causes conflicts. We only animate 5 frames,
// but have 50 stages, so conflicts stack up
// quickly.
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 50000 + 1);
}
TEST(
LayoutAnimationTest,
stableBiggerTreeFewRepeatsManyStages_Overlapping_ManyConflicts_2029343357) {
GTEST_SKIP();
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 128,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 10,
/* delay_ms_between_stages */ 10,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ (128 * 1000 + 100));
}
// You may uncomment this -
// locally only !-to generate failing seeds.
// TEST(
// LayoutAnimationTest,
// stableSmallerTreeFewRepeatsFewStages_Overlapping_Random) {
// std::random_device device;
// for (int i = 0; i < 10; i++) {
// uint_fast32_t seed = device();
// LOG(ERROR) << "Seed: " << seed;
// testShadowNodeTreeLifeCycleLayoutAnimations(
// /* seed */ seed,
// /* size */ 512,
// /* repeats */ 32,
// /* stages */ 128,
// /* animation_duration */ 1000,
// /* animation_frames*/ 10,
// /* delay_ms_between_frames */ 10,
// /* delay_ms_between_stages */ 10,
// /* delay_ms_between_repeats */ 2000,
// /* commits_conflicting_mutations */ true,
// /* final_animation_delay */ (128 * 1000 + 100));
// }
// // Fail if you want output to get seeds
// LOG(ERROR) << "ALL RUNS SUCCESSFUL";
// // react_native_assert(false);
// }

View File

@@ -0,0 +1,69 @@
/*
* 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 "utils.h"
#include <cmath>
namespace facebook::react {
std::pair<Float, Float> calculateAnimationProgress(
uint64_t now,
const LayoutAnimation& animation,
const AnimationConfig& mutationConfig) {
if (mutationConfig.animationType == AnimationType::None) {
return {1, 1};
}
uint64_t startTime = animation.startTime;
auto delay = (uint64_t)mutationConfig.delay;
uint64_t endTime = startTime + delay + (uint64_t)mutationConfig.duration;
if (now >= endTime) {
return {1, 1};
}
if (now < startTime + delay) {
return {0, 0};
}
double linearTimeProgression = 1 -
(double)(endTime - delay - now) / (double)(endTime - animation.startTime);
if (mutationConfig.animationType == AnimationType::Linear) {
return {linearTimeProgression, linearTimeProgression};
} else if (mutationConfig.animationType == AnimationType::EaseIn) {
// This is an accelerator-style interpolator.
// In the future, this parameter (2.0) could be adjusted. This has been the
// default for Classic RN forever.
return {linearTimeProgression, pow(linearTimeProgression, 2.0)};
} else if (mutationConfig.animationType == AnimationType::EaseOut) {
// This is an decelerator-style interpolator.
// In the future, this parameter (2.0) could be adjusted. This has been the
// default for Classic RN forever.
return {linearTimeProgression, 1.0 - pow(1 - linearTimeProgression, 2.0)};
} else if (mutationConfig.animationType == AnimationType::EaseInEaseOut) {
// This is a combination of accelerate+decelerate.
// The animation starts and ends slowly, and speeds up in the middle.
return {
linearTimeProgression,
cos((linearTimeProgression + 1.0) * M_PI) / 2 + 0.5};
} else if (mutationConfig.animationType == AnimationType::Spring) {
// Using mSpringDamping in this equation is not really the exact
// mathematical springDamping, but a good approximation We need to replace
// this equation with the right Factor that accounts for damping and
// friction
double damping = mutationConfig.springDamping;
return {
linearTimeProgression,
(1 +
pow(2, -10 * linearTimeProgression) *
sin((linearTimeProgression - damping / 4) * M_PI * 2 / damping))};
} else {
return {linearTimeProgression, linearTimeProgression};
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,105 @@
/*
* 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/animations/primitives.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
namespace facebook::react {
static inline bool shouldFirstComeBeforeSecondRemovesOnly(
const ShadowViewMutation &lhs,
const ShadowViewMutation &rhs) noexcept
{
// Make sure that removes on the same level are sorted - highest indices must
// come first.
return (lhs.type == ShadowViewMutation::Type::Remove && lhs.type == rhs.type) && (lhs.parentTag == rhs.parentTag) &&
(lhs.index > rhs.index);
}
static inline void handleShouldFirstComeBeforeSecondRemovesOnly(ShadowViewMutation::List &list) noexcept
{
std::unordered_map<std::string, std::vector<ShadowViewMutation>> removeMutationsByTag;
ShadowViewMutation::List finalList;
for (auto &mutation : list) {
if (mutation.type == ShadowViewMutation::Type::Remove) {
auto key = std::to_string(mutation.parentTag);
removeMutationsByTag[key].push_back(mutation);
} else {
finalList.push_back(mutation);
}
}
if (removeMutationsByTag.empty()) {
return;
}
for (auto &mutationsPair : removeMutationsByTag) {
if (mutationsPair.second.size() > 1) {
std::stable_sort(
mutationsPair.second.begin(), mutationsPair.second.end(), &shouldFirstComeBeforeSecondRemovesOnly);
}
finalList.insert(finalList.begin(), mutationsPair.second.begin(), mutationsPair.second.end());
}
list = finalList;
}
static inline bool shouldFirstComeBeforeSecondMutation(
const ShadowViewMutation &lhs,
const ShadowViewMutation &rhs) noexcept
{
if (lhs.type != rhs.type) {
// Deletes always come last
if (lhs.type == ShadowViewMutation::Type::Delete) {
return false;
}
if (rhs.type == ShadowViewMutation::Type::Delete) {
return true;
}
// Remove comes before insert
if (lhs.type == ShadowViewMutation::Type::Remove && rhs.type == ShadowViewMutation::Type::Insert) {
return true;
}
if (rhs.type == ShadowViewMutation::Type::Remove && lhs.type == ShadowViewMutation::Type::Insert) {
return false;
}
// Create comes before insert
if (lhs.type == ShadowViewMutation::Type::Create && rhs.type == ShadowViewMutation::Type::Insert) {
return true;
}
if (rhs.type == ShadowViewMutation::Type::Create && lhs.type == ShadowViewMutation::Type::Insert) {
return false;
}
// Remove comes before Update
if (lhs.type == ShadowViewMutation::Type::Remove && rhs.type == ShadowViewMutation::Type::Update) {
return true;
}
if (rhs.type == ShadowViewMutation::Type::Remove && lhs.type == ShadowViewMutation::Type::Update) {
return false;
}
} else {
// Make sure that removes on the same level are sorted - highest indices
// must come first.
if (lhs.type == ShadowViewMutation::Type::Remove && lhs.parentTag == rhs.parentTag) {
return lhs.index > rhs.index;
}
}
return &lhs < &rhs;
}
std::pair<Float, Float>
calculateAnimationProgress(uint64_t now, const LayoutAnimation &animation, const AnimationConfig &mutationConfig);
} // namespace facebook::react

View File

@@ -0,0 +1,152 @@
/*
* 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 "AttributedString.h"
#include <react/renderer/debug/DebugStringConvertibleItem.h>
namespace facebook::react {
using Fragment = AttributedString::Fragment;
using Fragments = AttributedString::Fragments;
#pragma mark - Fragment
std::string Fragment::AttachmentCharacter() {
// C++20 makes char8_t a distinct type from char, and u8 string literals
// consist of char8_t instead of char, which in turn requires std::u8string,
// etc. Here we were assuming char was UTF-8 anyway, so just cast to that
// (which is valid because char* is allowed to alias anything).
return reinterpret_cast<const char*>(
u8"\uFFFC"); // Unicode `OBJECT REPLACEMENT CHARACTER`
}
bool Fragment::isAttachment() const {
return string == AttachmentCharacter();
}
bool Fragment::operator==(const Fragment& rhs) const {
return std::tie(
string,
textAttributes,
parentShadowView.tag,
parentShadowView.layoutMetrics) ==
std::tie(
rhs.string,
rhs.textAttributes,
rhs.parentShadowView.tag,
rhs.parentShadowView.layoutMetrics);
}
bool Fragment::isContentEqual(const Fragment& rhs) const {
return std::tie(string, textAttributes) ==
std::tie(rhs.string, rhs.textAttributes);
}
#pragma mark - AttributedString
void AttributedString::appendFragment(Fragment&& fragment) {
ensureUnsealed();
if (!fragment.string.empty()) {
fragments_.push_back(std::move(fragment));
}
}
void AttributedString::prependFragment(Fragment&& fragment) {
ensureUnsealed();
if (!fragment.string.empty()) {
fragments_.insert(fragments_.begin(), std::move(fragment));
}
}
void AttributedString::setBaseTextAttributes(
const TextAttributes& defaultAttributes) {
baseAttributes_ = defaultAttributes;
}
const Fragments& AttributedString::getFragments() const {
return fragments_;
}
Fragments& AttributedString::getFragments() {
return fragments_;
}
std::string AttributedString::getString() const {
auto string = std::string{};
for (const auto& fragment : fragments_) {
string += fragment.string;
}
return string;
}
const TextAttributes& AttributedString::getBaseTextAttributes() const {
return baseAttributes_;
}
bool AttributedString::isEmpty() const {
return fragments_.empty();
}
bool AttributedString::compareTextAttributesWithoutFrame(
const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (fragments_[i].textAttributes != rhs.fragments_[i].textAttributes ||
fragments_[i].string != rhs.fragments_[i].string) {
return false;
}
}
return true;
}
bool AttributedString::operator==(const AttributedString& rhs) const {
return std::tie(fragments_, baseAttributes_) ==
std::tie(rhs.fragments_, rhs.baseAttributes_);
}
bool AttributedString::isContentEqual(const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (!fragments_[i].isContentEqual(rhs.fragments_[i])) {
return false;
}
}
return true;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList AttributedString::getDebugChildren() const {
auto list = SharedDebugStringConvertibleList{};
for (auto&& fragment : fragments_) {
auto propsList =
fragment.textAttributes.DebugStringConvertible::getDebugProps();
list.push_back(
std::make_shared<DebugStringConvertibleItem>(
"Fragment",
fragment.string,
SharedDebugStringConvertibleList(),
propsList));
}
return list;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,141 @@
/*
* 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/attributedstring/TextAttributes.h>
#include <react/renderer/core/Sealable.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/mounting/ShadowView.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Simple, cross-platform, React-specific implementation of attributed string
* (aka spanned string).
* `AttributedString` is basically a list of `Fragments` which have `string` and
* `textAttributes` + `shadowNode` associated with the `string`.
*/
class AttributedString : public Sealable, public DebugStringConvertible {
public:
class Fragment {
public:
static std::string AttachmentCharacter();
std::string string;
TextAttributes textAttributes;
ShadowView parentShadowView;
/*
* Returns true is the Fragment represents an attachment.
* Equivalent to `string == AttachmentCharacter()`.
*/
bool isAttachment() const;
/*
* Returns whether the underlying text and attributes are equal,
* disregarding layout or other information.
*/
bool isContentEqual(const Fragment &rhs) const;
bool operator==(const Fragment &rhs) const;
};
class Range {
public:
int location{0};
int length{0};
};
using Fragments = std::vector<Fragment>;
/*
* Appends and prepends a `fragment` to the string.
*/
void appendFragment(Fragment &&fragment);
void prependFragment(Fragment &&fragment);
/*
* Sets attributes which would apply to hypothetical text not included in the
* AttributedString.
*/
void setBaseTextAttributes(const TextAttributes &defaultAttributes);
/*
* Returns a read-only reference to a list of fragments.
*/
const Fragments &getFragments() const;
/*
* Returns a reference to a list of fragments.
*/
Fragments &getFragments();
/*
* Returns a string constructed from all strings in all fragments.
*/
std::string getString() const;
const TextAttributes &getBaseTextAttributes() const;
/*
* Returns `true` if the string is empty (has no any fragments).
*/
bool isEmpty() const;
/**
* Compares equality of TextAttributes of all Fragments on both sides.
*/
bool compareTextAttributesWithoutFrame(const AttributedString &rhs) const;
bool isContentEqual(const AttributedString &rhs) const;
bool operator==(const AttributedString &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugChildren() const override;
#endif
private:
Fragments fragments_;
TextAttributes baseAttributes_;
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::AttributedString::Fragment> {
size_t operator()(const facebook::react::AttributedString::Fragment &fragment) const
{
return facebook::react::hash_combine(
fragment.string,
fragment.textAttributes,
fragment.parentShadowView.tag,
fragment.parentShadowView.layoutMetrics);
}
};
template <>
struct hash<facebook::react::AttributedString> {
size_t operator()(const facebook::react::AttributedString &attributedString) const
{
auto seed = size_t{0};
facebook::react::hash_combine(seed, attributedString.getBaseTextAttributes());
for (const auto &fragment : attributedString.getFragments()) {
facebook::react::hash_combine(seed, fragment);
}
return seed;
}
};
} // namespace std

View File

@@ -0,0 +1,88 @@
/*
* 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 "AttributedStringBox.h"
#include <react/debug/react_native_assert.h>
#include <utility>
namespace facebook::react {
AttributedStringBox::AttributedStringBox()
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(AttributedString{})),
opaquePointer_({}) {};
AttributedStringBox::AttributedStringBox(const AttributedString& value)
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(value)),
opaquePointer_({}) {};
AttributedStringBox::AttributedStringBox(std::shared_ptr<void> opaquePointer)
: mode_(Mode::OpaquePointer),
value_({}),
opaquePointer_(std::move(opaquePointer)) {}
AttributedStringBox::AttributedStringBox(AttributedStringBox&& other) noexcept
: mode_(other.mode_),
value_(std::move(other.value_)),
opaquePointer_(std::move(other.opaquePointer_)) {
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
AttributedStringBox::Mode AttributedStringBox::getMode() const {
return mode_;
}
const AttributedString& AttributedStringBox::getValue() const {
react_native_assert(mode_ == AttributedStringBox::Mode::Value);
react_native_assert(value_);
return *value_;
}
std::shared_ptr<void> AttributedStringBox::getOpaquePointer() const {
react_native_assert(mode_ == AttributedStringBox::Mode::OpaquePointer);
react_native_assert(opaquePointer_);
return opaquePointer_;
}
AttributedStringBox& AttributedStringBox::operator=(
AttributedStringBox&& other) noexcept {
if (this != &other) {
mode_ = other.mode_;
value_ = std::move(other.value_);
opaquePointer_ = std::move(other.opaquePointer_);
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
return *this;
}
bool operator==(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
if (lhs.getMode() != rhs.getMode()) {
return false;
}
switch (lhs.getMode()) {
case AttributedStringBox::Mode::Value:
return lhs.getValue() == rhs.getValue();
case AttributedStringBox::Mode::OpaquePointer:
return lhs.getOpaquePointer() == rhs.getOpaquePointer();
}
}
bool operator!=(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
return !(lhs == rhs);
}
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
/*
* 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 <memory>
#include <react/renderer/attributedstring/AttributedString.h>
namespace facebook::react {
/*
* Represents an object storing a shared `AttributedString` or a shared pointer
* to some opaque platform-specific object that can be used as an attributed
* string. The class serves two main purposes:
* - Represent type-erased attributed string entity (which can be
* platform-specific or platform-independent);
* - Represent a container that can be copied with constant complexity.
*/
class AttributedStringBox final {
public:
enum class Mode { Value, OpaquePointer };
/*
* Default constructor constructs an empty string.
*/
AttributedStringBox();
/*
* Custom explicit constructors.
*/
explicit AttributedStringBox(const AttributedString &value);
explicit AttributedStringBox(std::shared_ptr<void> opaquePointer);
/*
* Movable, Copyable, Assignable.
*/
AttributedStringBox(const AttributedStringBox &other) = default;
AttributedStringBox(AttributedStringBox &&other) noexcept;
AttributedStringBox &operator=(const AttributedStringBox &other) = default;
AttributedStringBox &operator=(AttributedStringBox &&other) noexcept;
/*
* Getters.
*/
Mode getMode() const;
const AttributedString &getValue() const;
std::shared_ptr<void> getOpaquePointer() const;
private:
Mode mode_;
std::shared_ptr<const AttributedString> value_;
std::shared_ptr<void> opaquePointer_;
};
bool operator==(const AttributedStringBox &lhs, const AttributedStringBox &rhs);
bool operator!=(const AttributedStringBox &lhs, const AttributedStringBox &rhs);
} // namespace facebook::react
template <>
struct std::hash<facebook::react::AttributedStringBox> {
size_t operator()(const facebook::react::AttributedStringBox &attributedStringBox) const
{
switch (attributedStringBox.getMode()) {
case facebook::react::AttributedStringBox::Mode::Value:
return std::hash<facebook::react::AttributedString>()(attributedStringBox.getValue());
case facebook::react::AttributedStringBox::Mode::OpaquePointer:
return std::hash<std::shared_ptr<void>>()(attributedStringBox.getOpaquePointer());
}
}
};

View File

@@ -0,0 +1,31 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_attributedstring_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_attributedstring OBJECT ${react_renderer_attributedstring_SRC})
target_include_directories(react_renderer_attributedstring PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_attributedstring
folly_runtime
glog
glog_init
react_debug
rrc_view
react_renderer_core
react_renderer_debug
react_renderer_graphics
react_renderer_mapbuffer
react_utils
rrc_view
yoga
)
target_compile_reactnative_options(react_renderer_attributedstring PRIVATE)
target_compile_options(react_renderer_attributedstring PRIVATE -Wpedantic)

View File

@@ -0,0 +1,86 @@
/*
* 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 "ParagraphAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
return std::tie(
maximumNumberOfLines,
ellipsizeMode,
textBreakStrategy,
adjustsFontSizeToFit,
includeFontPadding,
android_hyphenationFrequency,
textAlignVertical) ==
std::tie(
rhs.maximumNumberOfLines,
rhs.ellipsizeMode,
rhs.textBreakStrategy,
rhs.adjustsFontSizeToFit,
rhs.includeFontPadding,
rhs.android_hyphenationFrequency,
rhs.textAlignVertical) &&
floatEquality(minimumFontSize, rhs.minimumFontSize) &&
floatEquality(maximumFontSize, rhs.maximumFontSize) &&
floatEquality(minimumFontScale, rhs.minimumFontScale);
}
bool ParagraphAttributes::operator!=(const ParagraphAttributes& rhs) const {
return !(*this == rhs);
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
ParagraphAttributes paragraphAttributes{};
return {
debugStringConvertibleItem(
"maximumNumberOfLines",
maximumNumberOfLines,
paragraphAttributes.maximumNumberOfLines),
debugStringConvertibleItem(
"ellipsizeMode", ellipsizeMode, paragraphAttributes.ellipsizeMode),
debugStringConvertibleItem(
"textBreakStrategy",
textBreakStrategy,
paragraphAttributes.textBreakStrategy),
debugStringConvertibleItem(
"adjustsFontSizeToFit",
adjustsFontSizeToFit,
paragraphAttributes.adjustsFontSizeToFit),
debugStringConvertibleItem(
"minimumFontSize",
minimumFontSize,
paragraphAttributes.minimumFontSize),
debugStringConvertibleItem(
"maximumFontSize",
maximumFontSize,
paragraphAttributes.maximumFontSize),
debugStringConvertibleItem(
"includeFontPadding",
includeFontPadding,
paragraphAttributes.includeFontPadding),
debugStringConvertibleItem(
"android_hyphenationFrequency",
android_hyphenationFrequency,
paragraphAttributes.android_hyphenationFrequency),
debugStringConvertibleItem(
"textAlignVertical",
textAlignVertical,
paragraphAttributes.textAlignVertical)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,116 @@
/*
* 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 <limits>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Float.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class ParagraphAttributes;
using SharedParagraphAttributes = std::shared_ptr<const ParagraphAttributes>;
/*
* Represents all visual attributes of a paragraph of text.
* Two data structures, ParagraphAttributes and AttributedText, should be
* enough to define visual representation of a piece of text on the screen.
*/
class ParagraphAttributes : public DebugStringConvertible {
public:
#pragma mark - Fields
/*
* Maximum number of lines which paragraph can take.
* Zero value represents "no limit".
*/
int maximumNumberOfLines{};
/*
* In case if a text cannot fit given boundaries, defines a place where
* an ellipsize should be placed.
*/
EllipsizeMode ellipsizeMode{};
/*
* (Android only) Break strategy for breaking paragraphs into lines.
*/
TextBreakStrategy textBreakStrategy{TextBreakStrategy::HighQuality};
/*
* Enables font size adjustment to fit constrained boundaries.
*/
bool adjustsFontSizeToFit{};
/*
* (Android only) Leaves enough room for ascenders and descenders instead of
* using the font ascent and descent strictly.
*/
bool includeFontPadding{true};
/*
* (Android only) Frequency of automatic hyphenation to use when determining
* word breaks.
*/
HyphenationFrequency android_hyphenationFrequency{};
/*
* In case of font size adjustment enabled, defines minimum and maximum
* font sizes.
*/
Float minimumFontSize{std::numeric_limits<Float>::quiet_NaN()};
Float maximumFontSize{std::numeric_limits<Float>::quiet_NaN()};
/*
* Specifies the smallest possible scale a font can reach when
* adjustsFontSizeToFit is enabled. (values 0.01-1.0).
*/
Float minimumFontScale{std::numeric_limits<Float>::quiet_NaN()};
/*
* The vertical alignment of the text, causing the glyphs to be vertically
* aligned in its container.
*/
std::optional<TextAlignmentVertical> textAlignVertical{};
bool operator==(const ParagraphAttributes &rhs) const;
bool operator!=(const ParagraphAttributes &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::ParagraphAttributes> {
size_t operator()(const facebook::react::ParagraphAttributes &attributes) const
{
return facebook::react::hash_combine(
attributes.maximumNumberOfLines,
attributes.ellipsizeMode,
attributes.textBreakStrategy,
attributes.adjustsFontSizeToFit,
attributes.minimumFontSize,
attributes.maximumFontSize,
attributes.includeFontPadding,
attributes.android_hyphenationFrequency,
attributes.minimumFontScale,
attributes.textAlignVertical);
}
};
} // namespace std

View File

@@ -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 <react/renderer/attributedstring/AttributedString.h>
namespace facebook::react {
/**
* Prior to D63303709 AttributedString could not represent formatting on an
* empty string, and so some text content was forcefully added to empty strings
* during measurement. Usages of this function should be replaced with
* formatting based off of baseTextAttributes.
*/
inline AttributedString ensurePlaceholderIfEmpty_DO_NOT_USE(const AttributedString &attributedString)
{
if (attributedString.isEmpty()) {
AttributedString placeholder{attributedString};
placeholder.appendFragment(
{.string = "I", .textAttributes = attributedString.getBaseTextAttributes(), .parentShadowView = {}});
return placeholder;
}
return attributedString;
}
} // namespace facebook::react

View File

@@ -0,0 +1,288 @@
/*
* 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 "TextAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/utils/FloatComparison.h>
#include <cmath>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
void TextAttributes::apply(TextAttributes textAttributes) {
// Color
foregroundColor = textAttributes.foregroundColor
? textAttributes.foregroundColor
: foregroundColor;
backgroundColor = textAttributes.backgroundColor
? textAttributes.backgroundColor
: backgroundColor;
opacity =
!std::isnan(textAttributes.opacity) ? textAttributes.opacity : opacity;
// Font
fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily
: fontFamily;
fontSize =
!std::isnan(textAttributes.fontSize) ? textAttributes.fontSize : fontSize;
fontSizeMultiplier = !std::isnan(textAttributes.fontSizeMultiplier)
? textAttributes.fontSizeMultiplier
: fontSizeMultiplier;
fontWeight = textAttributes.fontWeight.has_value() ? textAttributes.fontWeight
: fontWeight;
fontStyle = textAttributes.fontStyle.has_value() ? textAttributes.fontStyle
: fontStyle;
fontVariant = textAttributes.fontVariant.has_value()
? textAttributes.fontVariant
: fontVariant;
allowFontScaling = textAttributes.allowFontScaling.has_value()
? textAttributes.allowFontScaling
: allowFontScaling;
maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier)
? textAttributes.maxFontSizeMultiplier
: maxFontSizeMultiplier;
dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value()
? textAttributes.dynamicTypeRamp
: dynamicTypeRamp;
letterSpacing = !std::isnan(textAttributes.letterSpacing)
? textAttributes.letterSpacing
: letterSpacing;
textTransform = textAttributes.textTransform.has_value()
? textAttributes.textTransform
: textTransform;
// Paragraph Styles
lineHeight = !std::isnan(textAttributes.lineHeight)
? textAttributes.lineHeight
: lineHeight;
alignment = textAttributes.alignment.has_value() ? textAttributes.alignment
: alignment;
baseWritingDirection = textAttributes.baseWritingDirection.has_value()
? textAttributes.baseWritingDirection
: baseWritingDirection;
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
? textAttributes.lineBreakStrategy
: lineBreakStrategy;
lineBreakMode = textAttributes.lineBreakMode.has_value()
? textAttributes.lineBreakMode
: lineBreakMode;
// Decoration
textDecorationColor = textAttributes.textDecorationColor
? textAttributes.textDecorationColor
: textDecorationColor;
textDecorationLineType = textAttributes.textDecorationLineType.has_value()
? textAttributes.textDecorationLineType
: textDecorationLineType;
textDecorationStyle = textAttributes.textDecorationStyle.has_value()
? textAttributes.textDecorationStyle
: textDecorationStyle;
// Shadow
textShadowOffset = textAttributes.textShadowOffset.has_value()
? textAttributes.textShadowOffset.value()
: textShadowOffset;
textShadowRadius = !std::isnan(textAttributes.textShadowRadius)
? textAttributes.textShadowRadius
: textShadowRadius;
textShadowColor = textAttributes.textShadowColor
? textAttributes.textShadowColor
: textShadowColor;
// Special
isHighlighted = textAttributes.isHighlighted.has_value()
? textAttributes.isHighlighted
: isHighlighted;
// TextAttributes "inherits" the isPressable value from ancestors, so this
// only applies the current node's value for isPressable if it is truthy.
isPressable =
textAttributes.isPressable.has_value() && *textAttributes.isPressable
? textAttributes.isPressable
: isPressable;
layoutDirection = textAttributes.layoutDirection.has_value()
? textAttributes.layoutDirection
: layoutDirection;
accessibilityRole = textAttributes.accessibilityRole.has_value()
? textAttributes.accessibilityRole
: accessibilityRole;
role = textAttributes.role.has_value() ? textAttributes.role : role;
}
#pragma mark - Operators
bool TextAttributes::operator==(const TextAttributes& rhs) const {
return std::tie(
foregroundColor,
backgroundColor,
fontFamily,
fontWeight,
fontStyle,
fontVariant,
allowFontScaling,
dynamicTypeRamp,
alignment,
baseWritingDirection,
lineBreakStrategy,
textDecorationColor,
textDecorationLineType,
textDecorationStyle,
textShadowOffset,
textShadowColor,
isHighlighted,
isPressable,
layoutDirection,
accessibilityRole,
role,
textTransform) ==
std::tie(
rhs.foregroundColor,
rhs.backgroundColor,
rhs.fontFamily,
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment,
rhs.baseWritingDirection,
rhs.lineBreakStrategy,
rhs.textDecorationColor,
rhs.textDecorationLineType,
rhs.textDecorationStyle,
rhs.textShadowOffset,
rhs.textShadowColor,
rhs.isHighlighted,
rhs.isPressable,
rhs.layoutDirection,
rhs.accessibilityRole,
rhs.role,
rhs.textTransform) &&
floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) &&
floatEquality(opacity, rhs.opacity) &&
floatEquality(fontSize, rhs.fontSize) &&
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
floatEquality(letterSpacing, rhs.letterSpacing) &&
floatEquality(lineHeight, rhs.lineHeight) &&
floatEquality(textShadowRadius, rhs.textShadowRadius);
}
TextAttributes TextAttributes::defaultTextAttributes() {
static auto textAttributes = [] {
auto defaultAttrs = TextAttributes{};
// Non-obvious (can be different among platforms) default text attributes.
defaultAttrs.foregroundColor = blackColor();
defaultAttrs.backgroundColor = clearColor();
defaultAttrs.fontSize = 14.0;
defaultAttrs.fontSizeMultiplier = 1.0;
return defaultAttrs;
}();
return textAttributes;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
const auto& textAttributes = TextAttributes::defaultTextAttributes();
return {
// Color
debugStringConvertibleItem(
"backgroundColor", backgroundColor, textAttributes.backgroundColor),
debugStringConvertibleItem(
"foregroundColor", foregroundColor, textAttributes.foregroundColor),
debugStringConvertibleItem("opacity", opacity, textAttributes.opacity),
// Font
debugStringConvertibleItem(
"fontFamily", fontFamily, textAttributes.fontFamily),
debugStringConvertibleItem("fontSize", fontSize, textAttributes.fontSize),
debugStringConvertibleItem(
"fontSizeMultiplier",
fontSizeMultiplier,
textAttributes.fontSizeMultiplier),
debugStringConvertibleItem(
"fontWeight", fontWeight, textAttributes.fontWeight),
debugStringConvertibleItem(
"fontStyle", fontStyle, textAttributes.fontStyle),
debugStringConvertibleItem(
"fontVariant", fontVariant, textAttributes.fontVariant),
debugStringConvertibleItem(
"allowFontScaling",
allowFontScaling,
textAttributes.allowFontScaling),
debugStringConvertibleItem(
"maxFontSizeMultiplier",
maxFontSizeMultiplier,
textAttributes.maxFontSizeMultiplier),
debugStringConvertibleItem(
"dynamicTypeRamp", dynamicTypeRamp, textAttributes.dynamicTypeRamp),
debugStringConvertibleItem(
"letterSpacing", letterSpacing, textAttributes.letterSpacing),
// Paragraph Styles
debugStringConvertibleItem(
"lineHeight", lineHeight, textAttributes.lineHeight),
debugStringConvertibleItem(
"alignment", alignment, textAttributes.alignment),
debugStringConvertibleItem(
"writingDirection",
baseWritingDirection,
textAttributes.baseWritingDirection),
debugStringConvertibleItem(
"lineBreakStrategyIOS",
lineBreakStrategy,
textAttributes.lineBreakStrategy),
debugStringConvertibleItem(
"lineBreakModeIOS", lineBreakMode, textAttributes.lineBreakMode),
// Decoration
debugStringConvertibleItem(
"textDecorationColor",
textDecorationColor,
textAttributes.textDecorationColor),
debugStringConvertibleItem(
"textDecorationLineType",
textDecorationLineType,
textAttributes.textDecorationLineType),
debugStringConvertibleItem(
"textDecorationStyle",
textDecorationStyle,
textAttributes.textDecorationStyle),
// Shadow
debugStringConvertibleItem(
"textShadowOffset",
textShadowOffset,
textAttributes.textShadowOffset),
debugStringConvertibleItem(
"textShadowRadius",
textShadowRadius,
textAttributes.textShadowRadius),
debugStringConvertibleItem(
"textShadowColor", textShadowColor, textAttributes.textShadowColor),
// Special
debugStringConvertibleItem(
"isHighlighted", isHighlighted, textAttributes.isHighlighted),
debugStringConvertibleItem(
"isPressable", isPressable, textAttributes.isPressable),
debugStringConvertibleItem(
"layoutDirection", layoutDirection, textAttributes.layoutDirection),
debugStringConvertibleItem(
"accessibilityRole",
accessibilityRole,
textAttributes.accessibilityRole),
debugStringConvertibleItem("role", role, textAttributes.role),
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* 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 <functional>
#include <limits>
#include <optional>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/components/view/AccessibilityPrimitives.h>
#include <react/renderer/core/LayoutPrimitives.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Size.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class TextAttributes;
using SharedTextAttributes = std::shared_ptr<const TextAttributes>;
class TextAttributes : public DebugStringConvertible {
public:
/*
* Returns TextAttribute object which has actual default attribute values
* (e.g. `foregroundColor = black`), in oppose to TextAttribute's default
* constructor which creates an object with nulled attributes.
*/
static TextAttributes defaultTextAttributes();
#pragma mark - Fields
// Color
SharedColor foregroundColor{};
SharedColor backgroundColor{};
Float opacity{std::numeric_limits<Float>::quiet_NaN()};
// Font
std::string fontFamily{""};
Float fontSize{std::numeric_limits<Float>::quiet_NaN()};
Float fontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<FontWeight> fontWeight{};
std::optional<FontStyle> fontStyle{};
std::optional<FontVariant> fontVariant{};
std::optional<bool> allowFontScaling{};
Float maxFontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextTransform> textTransform{};
// Paragraph Styles
Float lineHeight{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextAlignment> alignment{};
std::optional<WritingDirection> baseWritingDirection{};
std::optional<LineBreakStrategy> lineBreakStrategy{};
std::optional<LineBreakMode> lineBreakMode{};
// Decoration
SharedColor textDecorationColor{};
std::optional<TextDecorationLineType> textDecorationLineType{};
std::optional<TextDecorationStyle> textDecorationStyle{};
// Shadow
// TODO: Use `Point` type instead of `Size` for `textShadowOffset` attribute.
std::optional<Size> textShadowOffset{};
Float textShadowRadius{std::numeric_limits<Float>::quiet_NaN()};
SharedColor textShadowColor{};
// Special
std::optional<bool> isHighlighted{};
std::optional<bool> isPressable{};
// TODO T59221129: document where this value comes from and how it is set.
// It's not clear if this is being used properly, or if it's being set at all.
// Currently, it is intentionally *not* being set as part of BaseTextProps
// construction.
std::optional<LayoutDirection> layoutDirection{};
std::optional<AccessibilityRole> accessibilityRole{};
std::optional<Role> role{};
#pragma mark - Operations
void apply(TextAttributes textAttributes);
#pragma mark - Operators
bool operator==(const TextAttributes &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::TextAttributes> {
size_t operator()(const facebook::react::TextAttributes &textAttributes) const
{
return facebook::react::hash_combine(
textAttributes.foregroundColor,
textAttributes.backgroundColor,
textAttributes.opacity,
textAttributes.fontFamily,
textAttributes.fontSize,
textAttributes.maxFontSizeMultiplier,
textAttributes.fontSizeMultiplier,
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.allowFontScaling,
textAttributes.letterSpacing,
textAttributes.textTransform,
textAttributes.lineHeight,
textAttributes.alignment,
textAttributes.baseWritingDirection,
textAttributes.lineBreakStrategy,
textAttributes.lineBreakMode,
textAttributes.textDecorationColor,
textAttributes.textDecorationLineType,
textAttributes.textDecorationStyle,
textAttributes.textShadowOffset,
textAttributes.textShadowRadius,
textAttributes.textShadowColor,
textAttributes.isHighlighted,
textAttributes.isPressable,
textAttributes.layoutDirection,
textAttributes.accessibilityRole,
textAttributes.role);
}
};
} // namespace std

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
/*
* 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 <functional>
#include <limits>
namespace facebook::react {
enum class FontStyle { Normal, Italic, Oblique };
enum class FontWeight : int {
Weight100 = 100,
UltraLight = 100,
Weight200 = 200,
Thin = 200,
Weight300 = 300,
Light = 300,
Weight400 = 400,
Regular = 400,
Weight500 = 500,
Medium = 500,
Weight600 = 600,
Semibold = 600,
Demibold = 600,
Weight700 = 700,
Bold = 700,
Weight800 = 800,
Heavy = 800,
Weight900 = 900,
Black = 900
};
enum class FontVariant : int {
Default = 0,
SmallCaps = 1 << 1,
OldstyleNums = 1 << 2,
LiningNums = 1 << 3,
TabularNums = 1 << 4,
ProportionalNums = 1 << 5,
StylisticOne = 1 << 6,
StylisticTwo = 1 << 7,
StylisticThree = 1 << 8,
StylisticFour = 1 << 9,
StylisticFive = 1 << 10,
StylisticSix = 1 << 11,
StylisticSeven = 1 << 12,
StylisticEight = 1 << 13,
StylisticNine = 1 << 14,
StylisticTen = 1 << 15,
StylisticEleven = 1 << 16,
StylisticTwelve = 1 << 17,
StylisticThirteen = 1 << 18,
StylisticFourteen = 1 << 19,
StylisticFifteen = 1 << 20,
StylisticSixteen = 1 << 21,
StylisticSeventeen = 1 << 22,
StylisticEighteen = 1 << 23,
StylisticNineteen = 1 << 24,
StylisticTwenty = 1 << 25
};
enum class DynamicTypeRamp {
Caption2,
Caption1,
Footnote,
Subheadline,
Callout,
Body,
Headline,
Title3,
Title2,
Title1,
LargeTitle
};
enum class EllipsizeMode {
Clip, // Do not add ellipsize, simply clip.
Head, // Truncate at head of line: "...wxyz".
Tail, // Truncate at tail of line: "abcd...".
Middle // Truncate middle of line: "ab...yz".
};
enum class TextBreakStrategy {
Simple, // Simple strategy.
HighQuality, // High-quality strategy, including hyphenation.
Balanced // Balances line lengths.
};
enum class TextAlignment {
Natural, // Indicates the default alignment for script.
Left, // Visually left aligned.
Center, // Visually centered.
Right, // Visually right aligned.
Justified // Fully-justified. The last line in a paragraph is natural-aligned.
};
enum class TextAlignmentVertical {
Auto,
Top,
Bottom,
Center,
};
enum class WritingDirection {
Natural, // Determines direction using the Unicode Bidi Algorithm rules P2 and
// P3.
LeftToRight, // Left to right writing direction.
RightToLeft // Right to left writing direction.
};
enum class LineBreakStrategy {
None, // Don't use any line break strategies
PushOut, // Use the push out line break strategy.
HangulWordPriority, // When specified, it prohibits breaking between Hangul
// characters.
Standard // Use the same configuration of line break strategies that the
// system uses for standard UI labels.
};
enum class LineBreakMode {
Word, // Wrap at word boundaries, default
Char, // Wrap at character boundaries
Clip, // Simply clip
Head, // Truncate at head of line: "...wxyz"
Middle, // Truncate middle of line: "ab...yz"
Tail // Truncate at tail of line: "abcd..."
};
enum class TextDecorationLineType { None, Underline, Strikethrough, UnderlineStrikethrough };
enum class TextDecorationStyle { Solid, Double, Dotted, Dashed };
enum class TextTransform {
None,
Uppercase,
Lowercase,
Capitalize,
Unset,
};
enum class HyphenationFrequency {
None, // No hyphenation.
Normal, // Less frequent hyphenation.
Full // Standard amount of hyphenation.
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* 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 <memory>
#include <gtest/gtest.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
namespace facebook::react {
TEST(AttributedStringBoxTest, testDefaultConstructor) {
auto attributedStringBox = AttributedStringBox{};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), AttributedString{});
}
TEST(AttributedStringBoxTest, testValueConstructor) {
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto attributedStringBox = AttributedStringBox{attributedString};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), attributedString);
}
TEST(AttributedStringBoxTest, testOpaquePointerConstructor) {
auto string = std::make_shared<std::string>("test string");
auto attributedStringBox = AttributedStringBox{string};
EXPECT_EQ(
attributedStringBox.getMode(), AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(attributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
TEST(AttributedStringBoxTest, testMoveConstructor) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(moveToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
TEST(AttributedStringBoxTest, testMoveAssignment) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto movedToAttributedStringBox = AttributedStringBox{};
movedToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
movedToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(movedToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox = AttributedStringBox{};
moveToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,15 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
add_library(react_renderer_bridging INTERFACE)
target_include_directories(react_renderer_bridging INTERFACE ${REACT_COMMON_DIR})
target_compile_reactnative_options(react_renderer_bridging INTERFACE)
target_compile_options(react_renderer_bridging INTERFACE -Wpedantic)

View File

@@ -0,0 +1,47 @@
/*
* 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 <jsi/jsi.h>
#include <react/bridging/Base.h>
#include <react/renderer/core/ShadowNode.h>
namespace facebook::react {
template <>
struct Bridging<std::shared_ptr<const ShadowNode>> {
static std::shared_ptr<const ShadowNode> fromJs(jsi::Runtime &rt, const jsi::Value &jsiValue)
{
auto object = jsiValue.asObject(rt);
// Using `jsi::NativeState` instead of `ShadowNodeWrapper` to avoid doing a
// dynamic pointer cast twice (as we're calling `hasNativeState` and then
// `getNativeState`). When we use `NativeState`, JSI doesn't need to do any
// dynamic casts and we can do a single one on our own after the checks.
if (!object.hasNativeState<jsi::NativeState>(rt)) {
throw jsi::JSINativeException("Value is not a ShadowNode reference");
}
auto nativeState = object.getNativeState<jsi::NativeState>(rt);
auto shadowNodeWrapper = std::dynamic_pointer_cast<ShadowNodeWrapper>(nativeState);
if (shadowNodeWrapper == nullptr || shadowNodeWrapper->shadowNode == nullptr) {
throw jsi::JSINativeException("Value state is nullptr, expected a ShadowNode reference");
}
return shadowNodeWrapper->shadowNode;
}
static jsi::Value toJs(jsi::Runtime &rt, const std::shared_ptr<const ShadowNode> &value)
{
jsi::Object obj(rt);
obj.setNativeState(rt, std::make_shared<ShadowNodeWrapper>(value));
return obj;
}
};
} // namespace facebook::react

View File

@@ -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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_componentregistry_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_componentregistry OBJECT ${react_renderer_componentregistry_SRC})
target_include_directories(react_renderer_componentregistry PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_componentregistry
folly_runtime
glog_init
jsi
react_debug
react_renderer_core
react_renderer_debug
react_utils
rrc_legacyviewmanagerinterop
)
target_compile_reactnative_options(react_renderer_componentregistry PRIVATE)
target_compile_options(react_renderer_componentregistry PRIVATE -Wpedantic)

View File

@@ -0,0 +1,31 @@
/*
* 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 <memory>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/core/EventDispatcher.h>
#include <react/utils/ContextContainer.h>
#include "ComponentDescriptorRegistry.h"
namespace facebook::react {
/**
* A factory to provide hosting app specific set of ComponentDescriptor's.
* Each app must provide an implementation of the static class method which
* should register its specific set of supported components.
*/
using ComponentRegistryFactory = std::function<SharedComponentDescriptorRegistry(
const EventDispatcher::Weak &eventDispatcher,
const std::shared_ptr<const ContextContainer> &contextContainer)>;
ComponentRegistryFactory getDefaultComponentRegistryFactory();
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* 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/core/ComponentDescriptor.h>
#include <react/renderer/core/EventDispatcher.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Callable signature that represents the signature of `ComponentDescriptor`
* constructor. The callable returns a unique pointer conveniently represents an
* abstract type and ownership of the newly created object.
*/
using ComponentDescriptorConstructor = ComponentDescriptor::Unique(const ComponentDescriptorParameters &parameters);
/*
* Represents a unified way to construct an instance of a particular stored
* `ComponentDescriptor` class. C++ does not allow to create pointers to
* constructors, so we have to have such data structure to manipulate a
* collection of classes.
*
* Note: The actual values of `handle` and `name` for some components depend on
* `flavor`. The provider is valid if instantiated by `constructor` object with
* given `flavor` exposes the same values of `handle` and `name`.
*/
class ComponentDescriptorProvider final {
public:
ComponentHandle handle;
ComponentName name;
ComponentDescriptor::Flavor flavor;
ComponentDescriptorConstructor *constructor;
};
/*
* Creates a `ComponentDescriptor` for given `ComponentDescriptorParameters`.
*/
template <typename ComponentDescriptorT>
ComponentDescriptor::Unique concreteComponentDescriptorConstructor(const ComponentDescriptorParameters &parameters)
{
static_assert(
std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
"ComponentDescriptorT must be a descendant of ComponentDescriptor");
return std::make_unique<const ComponentDescriptorT>(parameters);
}
/*
* Creates a `ComponentDescriptorProvider` for given `ComponentDescriptor`
* class.
*/
template <typename ComponentDescriptorT>
ComponentDescriptorProvider concreteComponentDescriptorProvider()
{
static_assert(
std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
"ComponentDescriptorT must be a descendant of ComponentDescriptor");
return {
ComponentDescriptorT::ConcreteShadowNode::Handle(),
ComponentDescriptorT::ConcreteShadowNode::Name(),
nullptr,
&concreteComponentDescriptorConstructor<ComponentDescriptorT>};
}
} // namespace facebook::react

View File

@@ -0,0 +1,83 @@
/*
* 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 "ComponentDescriptorProviderRegistry.h"
namespace facebook::react {
void ComponentDescriptorProviderRegistry::add(
const ComponentDescriptorProvider& provider) const {
std::unique_lock lock(mutex_);
/*
// TODO: T57583139
The assert is temporarily disabled to reduce the volume of the signal.
assert(
componentDescriptorProviders_.find(provider.handle) ==
componentDescriptorProviders_.end() &&
"Attempt to register an already registered ComponentDescriptorProvider.");
*/
if (componentDescriptorProviders_.find(provider.handle) !=
componentDescriptorProviders_.end()) {
// Re-registering a provider makes no sense because it's copyable: already
// registered one is as good as any new can be.
return;
}
componentDescriptorProviders_.insert({provider.handle, provider});
for (const auto& weakRegistry : componentDescriptorRegistries_) {
auto registry = weakRegistry.lock();
if (!registry) {
continue;
}
registry->add(provider);
}
}
void ComponentDescriptorProviderRegistry::setComponentDescriptorProviderRequest(
ComponentDescriptorProviderRequest componentDescriptorProviderRequest)
const {
std::shared_lock lock(mutex_);
componentDescriptorProviderRequest_ =
std::move(componentDescriptorProviderRequest);
}
void ComponentDescriptorProviderRegistry::request(
ComponentName componentName) const {
ComponentDescriptorProviderRequest componentDescriptorProviderRequest;
{
std::shared_lock lock(mutex_);
componentDescriptorProviderRequest = componentDescriptorProviderRequest_;
}
if (componentDescriptorProviderRequest) {
componentDescriptorProviderRequest(componentName);
}
}
ComponentDescriptorRegistry::Shared
ComponentDescriptorProviderRegistry::createComponentDescriptorRegistry(
const ComponentDescriptorParameters& parameters) const {
std::shared_lock lock(mutex_);
auto registry = std::make_shared<const ComponentDescriptorRegistry>(
parameters, *this, parameters.contextContainer);
for (const auto& pair : componentDescriptorProviders_) {
registry->add(pair.second);
}
componentDescriptorRegistries_.push_back(registry);
return registry;
}
} // namespace facebook::react

View File

@@ -0,0 +1,65 @@
/*
* 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 <shared_mutex>
#include <unordered_map>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/core/ComponentDescriptor.h>
namespace facebook::react {
using ComponentDescriptorProviderRequest = std::function<void(ComponentName componentName)>;
/*
* Registry of `ComponentDescriptorProvider`s (and managed
* `ComponentDescriptorRegistry`s). The class maintains a list of
* `ComponentDescriptorRegistry`s (retaining pointers weakly) and update them
* accordingly to changes in the provider registry.
*/
class ComponentDescriptorProviderRegistry final {
public:
/*
* Adds a `ComponentDescriptorProvider`s and update the managed
* `ComponentDescriptorRegistry`s accordingly.
* The methods can be called on any thread.
*/
void add(const ComponentDescriptorProvider &provider) const;
/*
* ComponenDescriptorRegistry will call the `request` in case if a component
* with given name wasn't registered yet.
* The request handler must register a ComponentDescriptor with requested name
* synchronously during handling the request.
* The request can be called on any thread.
* The methods can be called on any thread.
*/
void setComponentDescriptorProviderRequest(ComponentDescriptorProviderRequest request) const;
/*
* Creates managed `ComponentDescriptorRegistry` based on a stored list of
* `ComponentDescriptorProvider`s and given `ComponentDescriptorParameters`.
* The methods can be called on any thread.
*/
ComponentDescriptorRegistry::Shared createComponentDescriptorRegistry(
const ComponentDescriptorParameters &parameters) const;
private:
friend class ComponentDescriptorRegistry;
void request(ComponentName componentName) const;
mutable std::shared_mutex mutex_;
mutable std::vector<std::weak_ptr<const ComponentDescriptorRegistry>> componentDescriptorRegistries_;
mutable std::unordered_map<ComponentHandle, const ComponentDescriptorProvider> componentDescriptorProviders_;
mutable ComponentDescriptorProviderRequest componentDescriptorProviderRequest_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,155 @@
/*
* 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 "ComponentDescriptorRegistry.h"
#include "componentNameByReactViewName.h"
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticComponentDescriptor.h>
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <utility>
namespace facebook::react {
ComponentDescriptorRegistry::ComponentDescriptorRegistry(
ComponentDescriptorParameters parameters,
const ComponentDescriptorProviderRegistry& providerRegistry,
std::shared_ptr<const ContextContainer> contextContainer)
: parameters_(std::move(parameters)),
providerRegistry_(providerRegistry),
contextContainer_(std::move(contextContainer)) {}
void ComponentDescriptorRegistry::add(
const ComponentDescriptorProvider& componentDescriptorProvider) const {
std::unique_lock lock(mutex_);
auto componentDescriptor = componentDescriptorProvider.constructor(
{.eventDispatcher = parameters_.eventDispatcher,
.contextContainer = parameters_.contextContainer,
.flavor = componentDescriptorProvider.flavor});
react_native_assert(
componentDescriptor->getComponentHandle() ==
componentDescriptorProvider.handle);
react_native_assert(
componentDescriptor->getComponentName() ==
componentDescriptorProvider.name);
auto sharedComponentDescriptor = std::shared_ptr<const ComponentDescriptor>(
std::move(componentDescriptor));
_registryByHandle[componentDescriptorProvider.handle] =
sharedComponentDescriptor;
_registryByName[componentDescriptorProvider.name] = sharedComponentDescriptor;
}
void ComponentDescriptorRegistry::registerComponentDescriptor(
const SharedComponentDescriptor& componentDescriptor) const {
ComponentHandle componentHandle = componentDescriptor->getComponentHandle();
_registryByHandle[componentHandle] = componentDescriptor;
ComponentName componentName = componentDescriptor->getComponentName();
_registryByName[componentName] = componentDescriptor;
}
const ComponentDescriptor& ComponentDescriptorRegistry::at(
const std::string& componentName) const {
std::shared_lock lock(mutex_);
auto unifiedComponentName = componentNameByReactViewName(componentName);
auto it = _registryByName.find(unifiedComponentName);
if (it == _registryByName.end()) {
lock.unlock();
providerRegistry_.request(unifiedComponentName.c_str());
lock.lock();
it = _registryByName.find(unifiedComponentName);
/*
* TODO: T54849676
* Uncomment the `assert` after the following block that checks
* `_fallbackComponentDescriptor` is no longer needed. The assert assumes
* that `componentDescriptorProviderRequest` is always not null and register
* some component on every single request.
*/
// assert(it != _registryByName.end());
}
if (it == _registryByName.end()) {
if (ReactNativeFeatureFlags::useFabricInterop()) {
// When interop is enabled, if the component is not found we rely on
// UnstableLegacyViewManagerAutomaticComponentDescriptor to support legacy
// components in new architecture.
auto componentDescriptor = std::make_shared<
const UnstableLegacyViewManagerAutomaticComponentDescriptor>(
parameters_, unifiedComponentName);
registerComponentDescriptor(componentDescriptor);
return *_registryByName.find(unifiedComponentName)->second;
} else {
// When interop is disabled, if the component is not found we rely on
// fallbackComponentDescriptor (default:
// UnimplementedNativeViewComponentDescriptor).
// UnimplementedNativeViewComponentDescriptor displays a View in debug
// mode to alert the developer that the component is not properly
// configured, and an empty view in release mode.
if (_fallbackComponentDescriptor == nullptr) {
throw std::invalid_argument(
("Unable to find componentDescriptor for " + unifiedComponentName)
.c_str());
} else {
return *_fallbackComponentDescriptor.get();
}
}
}
return *it->second;
}
const ComponentDescriptor* ComponentDescriptorRegistry::
findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
auto iterator = _registryByHandle.find(componentHandle);
if (iterator == _registryByHandle.end()) {
return nullptr;
}
return iterator->second.get();
}
const ComponentDescriptor& ComponentDescriptorRegistry::at(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
return *_registryByHandle.at(componentHandle);
}
bool ComponentDescriptorRegistry::hasComponentDescriptorAt(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
auto iterator = _registryByHandle.find(componentHandle);
return iterator != _registryByHandle.end();
}
void ComponentDescriptorRegistry::setFallbackComponentDescriptor(
const SharedComponentDescriptor& descriptor) {
_fallbackComponentDescriptor = descriptor;
registerComponentDescriptor(descriptor);
}
ComponentDescriptor::Shared
ComponentDescriptorRegistry::getFallbackComponentDescriptor() const {
return _fallbackComponentDescriptor;
}
} // namespace facebook::react

View File

@@ -0,0 +1,81 @@
/*
* 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 <memory>
#include <shared_mutex>
#include <unordered_map>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/core/InstanceHandle.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class ComponentDescriptorProviderRegistry;
class ComponentDescriptorRegistry;
using SharedComponentDescriptorRegistry = std::shared_ptr<const ComponentDescriptorRegistry>;
/*
* Registry of particular `ComponentDescriptor`s.
*/
class ComponentDescriptorRegistry {
public:
using Shared = std::shared_ptr<const ComponentDescriptorRegistry>;
/*
* Creates an object with stored `ComponentDescriptorParameters` which will
* be used later to create `ComponentDescriptor`s.
*/
ComponentDescriptorRegistry(
ComponentDescriptorParameters parameters,
const ComponentDescriptorProviderRegistry &providerRegistry,
std::shared_ptr<const ContextContainer> contextContainer);
/*
* This is broken. Please do not use.
* If you requesting a ComponentDescriptor and unsure that it's there, you are
* doing something wrong.
*/
const ComponentDescriptor *findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
ComponentHandle componentHandle) const;
const ComponentDescriptor &at(const std::string &componentName) const;
const ComponentDescriptor &at(ComponentHandle componentHandle) const;
bool hasComponentDescriptorAt(ComponentHandle componentHandle) const;
void setFallbackComponentDescriptor(const SharedComponentDescriptor &descriptor);
ComponentDescriptor::Shared getFallbackComponentDescriptor() const;
private:
friend class ComponentDescriptorProviderRegistry;
void registerComponentDescriptor(const SharedComponentDescriptor &componentDescriptor) const;
/*
* Creates a `ComponentDescriptor` using specified
* `ComponentDescriptorProvider` and stored `ComponentDescriptorParameters`,
* and then adds that to the registry.
* To be used by `ComponentDescriptorProviderRegistry` only.
* Thread safe.
*/
void add(const ComponentDescriptorProvider &componentDescriptorProvider) const;
mutable std::shared_mutex mutex_;
mutable std::unordered_map<ComponentHandle, SharedComponentDescriptor> _registryByHandle;
mutable std::unordered_map<std::string, SharedComponentDescriptor> _registryByName;
ComponentDescriptor::Shared _fallbackComponentDescriptor;
ComponentDescriptorParameters parameters_{};
const ComponentDescriptorProviderRegistry &providerRegistry_;
std::shared_ptr<const ContextContainer> contextContainer_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* 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 "componentNameByReactViewName.h"
#include <algorithm>
namespace facebook::react {
std::string componentNameByReactViewName(std::string viewName) {
// We need this function only for the transition period;
// eventually, all names will be unified.
// TODO T97384889: unify component names between JS - Android - iOS - C++
std::string rctPrefix("RCT");
if (std::mismatch(rctPrefix.begin(), rctPrefix.end(), viewName.begin())
.first == rctPrefix.end()) {
// If `viewName` has "RCT" prefix, remove it.
viewName.erase(0, rctPrefix.length());
}
// Fabric uses slightly new names for Text components because of differences
// in semantic.
if (viewName == "Text") {
return "Paragraph";
}
// TODO T63839307: remove this condition after deleting TextInlineImage from
// old renderer code
if (viewName == "TextInlineImage") {
return "Image";
}
if (viewName == "VirtualText") {
return "Text";
}
if (viewName == "ImageView") {
return "Image";
}
if (viewName == "AndroidHorizontalScrollView") {
return "ScrollView";
}
if (viewName == "RKShimmeringView") {
return "ShimmeringView";
}
if (viewName == "RefreshControl") {
return "PullToRefreshView";
}
// We need this temporarily for testing purposes until we have proper
// implementation of core components.
// iOS-only
if (viewName == "ScrollContentView") {
return "View";
}
// iOS-only
if (viewName == "MultilineTextInputView" ||
viewName == "SinglelineTextInputView") {
return "TextInput";
}
return viewName;
}
} // namespace facebook::react

Some files were not shown because too many files have changed in this diff Show More