Files
Fluxup_PAP/node_modules/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
2026-03-10 16:18:05 +00:00

1138 lines
38 KiB
C++

/*
* 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 "NativeAnimatedNodesManager.h"
#include <cxxreact/TraceSection.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/animated/drivers/AnimationDriver.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/drivers/DecayAnimationDriver.h>
#include <react/renderer/animated/drivers/FrameAnimationDriver.h>
#include <react/renderer/animated/drivers/SpringAnimationDriver.h>
#include <react/renderer/animated/nodes/AdditionAnimatedNode.h>
#include <react/renderer/animated/nodes/AnimatedNode.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/DiffClampAnimatedNode.h>
#include <react/renderer/animated/nodes/DivisionAnimatedNode.h>
#include <react/renderer/animated/nodes/InterpolationAnimatedNode.h>
#include <react/renderer/animated/nodes/ModulusAnimatedNode.h>
#include <react/renderer/animated/nodes/MultiplicationAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/animated/nodes/PropsAnimatedNode.h>
#include <react/renderer/animated/nodes/RoundAnimatedNode.h>
#include <react/renderer/animated/nodes/StyleAnimatedNode.h>
#include <react/renderer/animated/nodes/SubtractionAnimatedNode.h>
#include <react/renderer/animated/nodes/TrackingAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <react/renderer/core/EventEmitter.h>
#ifdef RN_USE_ANIMATION_BACKEND
#include <react/renderer/animationbackend/AnimatedPropsBuilder.h>
#endif
namespace facebook::react {
// Global function pointer for getting current time. Current time
// can be injected for testing purposes.
static TimePointFunction g_now = &std::chrono::steady_clock::now;
void g_setNativeAnimatedNowTimestampFunction(TimePointFunction nowFunction) {
g_now = nowFunction;
}
namespace {
struct NodesQueueItem {
AnimatedNode* node;
bool connectedToFinishedAnimation;
};
void mergeObjects(folly::dynamic& out, const folly::dynamic& objectToMerge) {
react_native_assert(objectToMerge.isObject());
if (out.isObject() && !out.empty()) {
for (const auto& pair : objectToMerge.items()) {
out[pair.first] = pair.second;
}
} else {
out = objectToMerge;
}
}
} // namespace
thread_local bool NativeAnimatedNodesManager::isOnRenderThread_{false};
NativeAnimatedNodesManager::NativeAnimatedNodesManager(
DirectManipulationCallback&& directManipulationCallback,
FabricCommitCallback&& fabricCommitCallback,
StartOnRenderCallback&& startOnRenderCallback,
StopOnRenderCallback&& stopOnRenderCallback,
FrameRateListenerCallback&& frameRateListenerCallback) noexcept
: directManipulationCallback_(std::move(directManipulationCallback)),
fabricCommitCallback_(std::move(fabricCommitCallback)),
startOnRenderCallback_(std::move(startOnRenderCallback)),
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
frameRateListenerCallback_(std::move(frameRateListenerCallback)) {
if (!fabricCommitCallback_) {
LOG(WARNING)
<< "C++ Animated was setup without commit callback. This may lead to issue where buttons are not tappable when animation is driven by onScroll event.";
}
if (!directManipulationCallback_) {
LOG(WARNING)
<< "C++ Animated was setup without direct manipulation callback. This may lead to suboptimal performance.";
}
if (!directManipulationCallback_ && fabricCommitCallback_) {
LOG(ERROR)
<< "C++ Animated was setup without a way to update UI. Animations will not work.";
}
}
NativeAnimatedNodesManager::NativeAnimatedNodesManager(
std::shared_ptr<UIManagerAnimationBackend> animationBackend) noexcept
: animationBackend_(animationBackend) {}
NativeAnimatedNodesManager::~NativeAnimatedNodesManager() noexcept {
stopRenderCallbackIfNeeded(true);
}
std::optional<double> NativeAnimatedNodesManager::getValue(Tag tag) noexcept {
auto node = getAnimatedNode<ValueAnimatedNode>(tag);
if (node != nullptr) {
return node->getValue();
} else {
LOG(WARNING)
<< "Cannot get value from AnimatedNode, it's not a ValueAnimatedNode";
return std::nullopt;
}
}
#pragma mark - Graph
std::unique_ptr<AnimatedNode> NativeAnimatedNodesManager::animatedNode(
Tag tag,
const folly::dynamic& config) noexcept {
auto typeName = config["type"].asString();
auto type = AnimatedNode::getNodeTypeByName(typeName);
if (!type) {
LOG(WARNING) << "Invalid AnimatedNode type " << typeName;
return nullptr;
}
switch (type.value()) {
case AnimatedNodeType::Style:
return std::make_unique<StyleAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Value:
return std::make_unique<ValueAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Color:
return std::make_unique<ColorAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Props:
return std::make_unique<PropsAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Tracking:
return std::make_unique<TrackingAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Interpolation:
return std::make_unique<InterpolationAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Transform:
return std::make_unique<TransformAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Subtraction:
return std::make_unique<SubtractionAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Addition:
return std::make_unique<AdditionAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Multiplication:
return std::make_unique<MultiplicationAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Division:
return std::make_unique<DivisionAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Modulus:
return std::make_unique<ModulusAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Diffclamp:
return std::make_unique<DiffClampAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Round:
return std::make_unique<RoundAnimatedNode>(tag, config, *this);
case AnimatedNodeType::Object:
return std::make_unique<ObjectAnimatedNode>(tag, config, *this);
default:
LOG(WARNING) << "Cannot create AnimatedNode of type " << typeName
<< ", it's not implemented yet";
return nullptr;
}
}
void NativeAnimatedNodesManager::createAnimatedNodeAsync(
Tag tag,
const folly::dynamic& config) noexcept {
if (isOnRenderThread_) {
LOG(ERROR)
<< "createAnimatedNodeAsync should not be called on render thread";
return;
}
auto node = animatedNode(tag, config);
if (node) {
std::lock_guard<std::mutex> lock(animatedNodesCreatedAsyncMutex_);
animatedNodesCreatedAsync_.emplace(tag, std::move(node));
}
}
void NativeAnimatedNodesManager::createAnimatedNode(
Tag tag,
const folly::dynamic& config) noexcept {
if (!isOnRenderThread_) {
LOG(ERROR) << "createAnimatedNode should only be called on render thread";
return;
}
auto node = animatedNode(tag, config);
if (node) {
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
animatedNodes_.emplace(tag, std::move(node));
updatedNodeTags_.insert(tag);
}
}
void NativeAnimatedNodesManager::connectAnimatedNodes(
Tag parentTag,
Tag childTag) noexcept {
react_native_assert(parentTag);
react_native_assert(childTag);
auto parentNode = getAnimatedNode<AnimatedNode>(parentTag);
auto childNode = getAnimatedNode<AnimatedNode>(childTag);
if ((parentNode != nullptr) && (childNode != nullptr)) {
parentNode->addChild(childTag);
updatedNodeTags_.insert(childTag);
} else {
LOG(WARNING) << "Cannot ConnectAnimatedNodes, parentTag = " << parentTag
<< ", childTag = " << childTag
<< ", not all of them are created";
}
}
void NativeAnimatedNodesManager::connectAnimatedNodeToView(
Tag propsNodeTag,
Tag viewTag) noexcept {
react_native_assert(propsNodeTag);
react_native_assert(viewTag);
auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
if (node != nullptr) {
node->connectToView(viewTag);
{
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
connectedAnimatedNodes_.insert({viewTag, propsNodeTag});
}
updatedNodeTags_.insert(node->tag());
} else {
LOG(WARNING)
<< "Cannot ConnectAnimatedNodeToView, animated node has to be props type";
}
}
void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
Tag propsNodeTag,
Tag viewTag) noexcept {
react_native_assert(propsNodeTag);
react_native_assert(viewTag);
auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
if (node != nullptr) {
node->disconnectFromView(viewTag);
{
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
connectedAnimatedNodes_.erase(viewTag);
}
updatedNodeTags_.insert(node->tag());
onManagedPropsRemoved(viewTag);
} else {
LOG(WARNING)
<< "Cannot DisconnectAnimatedNodeToView, animated node has to be props type";
}
}
void NativeAnimatedNodesManager::disconnectAnimatedNodes(
Tag parentTag,
Tag childTag) noexcept {
react_native_assert(parentTag);
react_native_assert(childTag);
auto parentNode = getAnimatedNode<AnimatedNode>(parentTag);
auto childNode = getAnimatedNode<AnimatedNode>(childTag);
if ((parentNode != nullptr) && (childNode != nullptr)) {
parentNode->removeChild(childTag);
} else {
LOG(WARNING) << "Cannot DisconnectAnimatedNodes, parentTag = " << parentTag
<< ", childTag = " << childTag
<< ", not all of them are created";
}
}
void NativeAnimatedNodesManager::restoreDefaultValues(Tag tag) noexcept {
if (auto propsNode = getAnimatedNode<PropsAnimatedNode>(tag)) {
propsNode->restoreDefaultValues();
}
}
void NativeAnimatedNodesManager::dropAnimatedNode(Tag tag) noexcept {
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
animatedNodes_.erase(tag);
}
#pragma mark - Mutations
void NativeAnimatedNodesManager::setAnimatedNodeValue(Tag tag, double value) {
if (auto node = getAnimatedNode<ValueAnimatedNode>(tag)) {
stopAnimationsForNode(node->tag());
if (node->setRawValue(value)) {
updatedNodeTags_.insert(node->tag());
}
}
}
void NativeAnimatedNodesManager::setAnimatedNodeOffset(Tag tag, double offset) {
if (auto node = getAnimatedNode<ValueAnimatedNode>(tag)) {
if (node->setOffset(offset)) {
updatedNodeTags_.insert(node->tag());
}
}
}
void NativeAnimatedNodesManager::flattenAnimatedNodeOffset(Tag tag) {
if (auto node = getAnimatedNode<ValueAnimatedNode>(tag)) {
node->flattenOffset();
}
}
void NativeAnimatedNodesManager::extractAnimatedNodeOffsetOp(Tag tag) {
if (auto node = getAnimatedNode<ValueAnimatedNode>(tag)) {
node->extractOffset();
}
}
void NativeAnimatedNodesManager::stopAnimationsForNode(Tag nodeTag) {
std::vector<int> discardedAnimIds{};
for (const auto& [animationId, driver] : activeAnimations_) {
if (driver->getAnimatedValueTag() == nodeTag) {
discardedAnimIds.emplace_back(animationId);
}
}
for (const auto& id : discardedAnimIds) {
activeAnimations_.at(id)->stopAnimation();
activeAnimations_.erase(id);
}
}
#pragma mark - Drivers
void NativeAnimatedNodesManager::startAnimatingNode(
int animationId,
Tag animatedNodeTag,
folly::dynamic config,
std::optional<AnimationEndCallback> endCallback) noexcept {
if (auto iter = activeAnimations_.find(animationId);
iter != activeAnimations_.end()) {
// reset animation config
auto& animation = iter->second;
animation->updateConfig(config);
} else if (animatedNodes_.contains(animatedNodeTag)) {
auto type = config["type"].asString();
auto typeEnum = AnimationDriver::getDriverTypeByName(type);
std::unique_ptr<AnimationDriver> animation = nullptr;
if (typeEnum) {
switch (typeEnum.value()) {
case AnimationDriverType::Frames: {
animation = std::make_unique<FrameAnimationDriver>(
animationId,
animatedNodeTag,
std::move(endCallback),
std::move(config),
this);
} break;
case AnimationDriverType::Spring: {
animation = std::make_unique<SpringAnimationDriver>(
animationId,
animatedNodeTag,
std::move(endCallback),
std::move(config),
this);
} break;
case AnimationDriverType::Decay: {
animation = std::make_unique<DecayAnimationDriver>(
animationId,
animatedNodeTag,
std::move(endCallback),
std::move(config),
this);
} break;
}
if (animation) {
animation->startAnimation();
activeAnimations_.insert({animationId, std::move(animation)});
}
} else {
LOG(ERROR) << "Unknown AnimationDriver type " << type;
}
}
}
void NativeAnimatedNodesManager::stopAnimation(
int animationId,
bool /*isTrackingAnimation*/) noexcept {
if (auto iter = activeAnimations_.find(animationId);
iter != activeAnimations_.end()) {
iter->second->stopAnimation();
activeAnimations_.erase(iter);
}
}
void NativeAnimatedNodesManager::addAnimatedEventToView(
Tag viewTag,
const std::string& eventName,
const folly::dynamic& eventMapping) noexcept {
const auto animatedValueTag = (eventMapping.count("animatedValueTag") != 0u)
? static_cast<Tag>(eventMapping["animatedValueTag"].asInt())
: 0;
const auto& pathList = eventMapping["nativeEventPath"];
auto numPaths = pathList.size();
std::vector<std::string> eventPath(numPaths);
for (size_t i = 0; i < numPaths; i++) {
eventPath[i] = pathList[i].asString();
}
const auto key = EventAnimationDriverKey{
.viewTag = viewTag,
.eventName = EventEmitter::normalizeEventType(eventName)};
if (auto driversIter = eventDrivers_.find(key);
driversIter != eventDrivers_.end()) {
auto& drivers = driversIter->second;
drivers.emplace_back(
std::make_unique<EventAnimationDriver>(eventPath, animatedValueTag));
} else {
std::vector<std::unique_ptr<EventAnimationDriver>> drivers(1);
drivers[0] =
std::make_unique<EventAnimationDriver>(eventPath, animatedValueTag);
eventDrivers_.insert({key, std::move(drivers)});
}
}
void NativeAnimatedNodesManager::removeAnimatedEventFromView(
Tag viewTag,
const std::string& eventName,
Tag animatedValueTag) noexcept {
const auto key = EventAnimationDriverKey{
.viewTag = viewTag,
.eventName = EventEmitter::normalizeEventType(eventName)};
auto driversIter = eventDrivers_.find(key);
if (driversIter != eventDrivers_.end()) {
auto& drivers = driversIter->second;
std::erase_if(drivers, [animatedValueTag](auto& it) {
return it->getAnimatedNodeTag() == animatedValueTag;
});
}
}
void NativeAnimatedNodesManager::handleAnimatedEvent(
Tag viewTag,
const std::string& eventName,
const EventPayload& eventPayload) noexcept {
// We currently reject events that are not on the same thread as `onRender`
// callbacks, as the assumption is these events can synchronously update
// UI components or otherwise Animated nodes with single-threaded assumptions.
// While we could dispatch event handling back to the UI thread using the
// scheduleOnUI helper, we are not yet doing that because it would violate
// the assumption that the events have synchronous side-effects. We can
// revisit this decision later.
if (!isOnRenderThread_) {
return;
}
if (eventDrivers_.empty()) {
return;
}
bool foundAtLeastOneDriver = false;
const auto key = EventAnimationDriverKey{
.viewTag = viewTag,
.eventName = EventEmitter::normalizeEventType(eventName)};
if (auto driversIter = eventDrivers_.find(key);
driversIter != eventDrivers_.end()) {
auto& drivers = driversIter->second;
if (!drivers.empty()) {
foundAtLeastOneDriver = true;
}
for (const auto& driver : drivers) {
if (auto value = driver->getValueFromPayload(eventPayload)) {
auto node =
getAnimatedNode<ValueAnimatedNode>(driver->getAnimatedNodeTag());
if (node == nullptr) {
continue;
}
stopAnimationsForNode(node->tag());
if (node->setRawValue(value.value())) {
updatedNodeTags_.insert(node->tag());
}
}
}
}
if (foundAtLeastOneDriver && !isEventAnimationInProgress_) {
// There is an animation driver handling this event and
// event driven animation has not been started yet.
isEventAnimationInProgress_ = true;
// Some platforms (e.g. iOS) have UI tick listener disable
// when there are no active animations. Calling
// `startRenderCallbackIfNeeded` will call platform specific code to
// register UI tick listener.
startRenderCallbackIfNeeded(false);
// Calling startOnRenderCallback_ will register a UI tick listener.
// The UI ticker listener will not be called until the next frame.
// That's why, in case this is called from the UI thread, we need to
// proactivelly trigger the animation loop to avoid showing stale
// frames.
onRender();
}
}
std::shared_ptr<EventEmitterListener>
NativeAnimatedNodesManager::ensureEventEmitterListener() noexcept {
if (!eventEmitterListener_) {
eventEmitterListener_ = std::make_shared<EventEmitterListener>(
[this](
Tag tag,
const std::string& eventName,
const EventPayload& payload) -> bool {
handleAnimatedEvent(tag, eventName, payload);
return false;
});
}
return eventEmitterListener_;
}
void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
#ifdef RN_USE_ANIMATION_BACKEND
if (auto animationBackend = animationBackend_.lock()) {
std::static_pointer_cast<AnimationBackend>(animationBackend)
->start(
[this](float /*f*/) { return pullAnimationMutations(); },
isAsync);
}
#endif
return;
}
// This method can be called from either the UI thread or JavaScript thread.
// It ensures `startOnRenderCallback_` is called exactly once using atomic
// operations. We use std::atomic_bool rather than std::mutex to avoid
// potential deadlocks that could occur if we called external code while
// holding a mutex.
auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(true);
if (isRenderCallbackStarted) {
// onRender callback is already started.
return;
}
if (startOnRenderCallback_) {
startOnRenderCallback_([this]() { onRender(); }, isAsync);
}
}
void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded(
bool isAsync) noexcept {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
if (auto animationBackend = animationBackend_.lock()) {
animationBackend->stop(isAsync);
}
return;
}
// When multiple threads reach this point, only one thread should call
// stopOnRenderCallback_. This synchronization is primarily needed during
// destruction of NativeAnimatedNodesManager. In normal operation,
// stopRenderCallbackIfNeeded is always called from the UI thread.
auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(false);
if (isRenderCallbackStarted) {
if (stopOnRenderCallback_) {
stopOnRenderCallback_(isAsync);
}
if (frameRateListenerCallback_) {
frameRateListenerCallback_(false);
}
}
}
bool NativeAnimatedNodesManager::isAnimationUpdateNeeded() const noexcept {
return !activeAnimations_.empty() || !updatedNodeTags_.empty() ||
isEventAnimationInProgress_;
}
void NativeAnimatedNodesManager::updateNodes(
const std::set<int>& finishedAnimationValueNodes) noexcept {
auto nodesQueue = std::deque<NodesQueueItem>{};
const auto is_node_connected_to_finished_animation =
[&finishedAnimationValueNodes](
AnimatedNode* node,
int nodeTag,
bool parentFinishedAnimation) -> bool {
return parentFinishedAnimation ||
(node->type() == AnimatedNodeType::Value &&
finishedAnimationValueNodes.contains(nodeTag));
};
#ifdef REACT_NATIVE_DEBUG
int activeNodesCount = 0;
int updatedNodesCount = 0;
#endif
// STEP 1.
// BFS over graph of nodes. Update `mIncomingNodes` attribute for each node
// during that BFS. Store number of visited nodes in `activeNodesCount`. We
// "execute" active animations as a part of this step.
animatedGraphBFSColor_++;
if (animatedGraphBFSColor_ == AnimatedNode::INITIAL_BFS_COLOR) {
animatedGraphBFSColor_++;
}
for (const auto& nodeTag : updatedNodeTags_) {
if (auto node = getAnimatedNode<AnimatedNode>(nodeTag)) {
if (node->bfsColor != animatedGraphBFSColor_) {
node->bfsColor = animatedGraphBFSColor_;
#ifdef REACT_NATIVE_DEBUG
activeNodesCount++;
#endif
const auto connectedToFinishedAnimation =
is_node_connected_to_finished_animation(node, nodeTag, false);
nodesQueue.emplace_back(
NodesQueueItem{
.node = node,
.connectedToFinishedAnimation = connectedToFinishedAnimation});
}
}
}
while (!nodesQueue.empty()) {
auto nextNode = nodesQueue.front();
nodesQueue.pop_front();
// in Animated, value nodes like RGBA are parents and Color node is child
// (the opposite of tree structure)
for (const auto childTag : nextNode.node->getChildren()) {
auto child = getAnimatedNode<AnimatedNode>(childTag);
child->activeIncomingNodes++;
if (child->bfsColor != animatedGraphBFSColor_) {
child->bfsColor = animatedGraphBFSColor_;
#ifdef REACT_NATIVE_DEBUG
activeNodesCount++;
#endif
const auto connectedToFinishedAnimation =
is_node_connected_to_finished_animation(
child, childTag, nextNode.connectedToFinishedAnimation);
nodesQueue.emplace_back(
NodesQueueItem{
.node = child,
.connectedToFinishedAnimation = connectedToFinishedAnimation});
}
}
}
// STEP 2
// BFS over the graph of active nodes in topological order -> visit node only
// when all its "predecessors" in the graph have already been visited. It is
// important to visit nodes in that order as they may often use values of
// their predecessors in order to calculate "next state" of their own. We
// start by determining the starting set of nodes by looking for nodes with
// `activeIncomingNodes = 0` (those can only be the ones that we start BFS in
// the previous step). We store number of visited nodes in this step in
// `updatedNodesCount`
animatedGraphBFSColor_++;
if (animatedGraphBFSColor_ == AnimatedNode::INITIAL_BFS_COLOR) {
animatedGraphBFSColor_++;
}
for (const auto& nodeTag : updatedNodeTags_) {
if (auto node = getAnimatedNode<AnimatedNode>(nodeTag)) {
if (node->activeIncomingNodes == 0 &&
node->bfsColor != animatedGraphBFSColor_) {
node->bfsColor = animatedGraphBFSColor_;
#ifdef REACT_NATIVE_DEBUG
updatedNodesCount++;
#endif
const auto connectedToFinishedAnimation =
is_node_connected_to_finished_animation(node, nodeTag, false);
nodesQueue.emplace_back(
NodesQueueItem{
.node = node,
.connectedToFinishedAnimation = connectedToFinishedAnimation});
}
}
}
// Run main "update" loop
#ifdef REACT_NATIVE_DEBUG
int cyclesDetected = 0;
#endif
while (!nodesQueue.empty()) {
auto nextNode = nodesQueue.front();
nodesQueue.pop_front();
if (nextNode.connectedToFinishedAnimation &&
nextNode.node->type() == AnimatedNodeType::Props) {
if (auto propsNode = dynamic_cast<PropsAnimatedNode*>(nextNode.node)) {
propsNode->update(/*forceFabricCommit*/ true);
};
} else {
nextNode.node->update();
}
for (auto childTag : nextNode.node->getChildren()) {
auto child = getAnimatedNode<AnimatedNode>(childTag);
child->activeIncomingNodes--;
if (child->activeIncomingNodes == 0 && child->activeIncomingNodes == 0) {
child->bfsColor = animatedGraphBFSColor_;
#ifdef REACT_NATIVE_DEBUG
updatedNodesCount++;
#endif
const auto connectedToFinishedAnimation =
is_node_connected_to_finished_animation(
child, childTag, nextNode.connectedToFinishedAnimation);
nodesQueue.emplace_back(
NodesQueueItem{
.node = child,
.connectedToFinishedAnimation = connectedToFinishedAnimation});
}
#ifdef REACT_NATIVE_DEBUG
else if (child->bfsColor == animatedGraphBFSColor_) {
cyclesDetected++;
}
#endif
}
}
#ifdef REACT_NATIVE_DEBUG
// Verify that we've visited *all* active nodes. Throw otherwise as this could
// mean there is a cycle in animated node graph, or that the graph is only
// partially set up. We also take advantage of the fact that all active nodes
// are visited in the step above so that all the nodes properties
// `activeIncomingNodes` are set to zero. In Fabric there can be race
// conditions between the JS thread setting up or tearing down animated nodes,
// and Fabric executing them on the UI thread, leading to temporary
// inconsistent states.
if (activeNodesCount != updatedNodesCount) {
if (warnedAboutGraphTraversal_) {
return;
}
warnedAboutGraphTraversal_ = true;
auto reason = cyclesDetected > 0
? ("cycles (" + std::to_string(cyclesDetected) + ")")
: "disconnected regions";
LOG(ERROR) << "Detected animation cycle or disconnected graph. "
<< "Looks like animated nodes graph has " << reason
<< ", there are " << activeNodesCount
<< " but toposort visited only " << updatedNodesCount;
} else {
warnedAboutGraphTraversal_ = false;
}
#endif
updatedNodeTags_.clear();
}
bool NativeAnimatedNodesManager::onAnimationFrame(double timestamp) {
// Run all active animations
auto hasFinishedAnimations = false;
std::set<int> finishedAnimationValueNodes;
for (const auto& [_id, driver] : activeAnimations_) {
driver->runAnimationStep(timestamp);
if (driver->getIsComplete()) {
hasFinishedAnimations = true;
const auto shouldRemoveJsSync =
ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync() &&
!ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated();
if (shouldRemoveJsSync) {
finishedAnimationValueNodes.insert(driver->getAnimatedValueTag());
}
}
}
// Update all animated nodes
updateNodes(finishedAnimationValueNodes);
// remove finished animations
if (hasFinishedAnimations) {
std::vector<int> finishedAnimations;
for (const auto& [animationId, driver] : activeAnimations_) {
if (driver->getIsComplete()) {
if (getAnimatedNode<ValueAnimatedNode>(driver->getAnimatedValueTag()) !=
nullptr) {
driver->stopAnimation();
}
finishedAnimations.emplace_back(animationId);
}
}
for (const auto& id : finishedAnimations) {
activeAnimations_.erase(id);
}
}
return commitProps();
}
folly::dynamic NativeAnimatedNodesManager::managedProps(
Tag tag) const noexcept {
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
if (const auto iter = connectedAnimatedNodes_.find(tag);
iter != connectedAnimatedNodes_.end()) {
if (const auto node = getAnimatedNode<PropsAnimatedNode>(iter->second)) {
return node->props();
}
} else if (!ReactNativeFeatureFlags::
overrideBySynchronousMountPropsAtMountingAndroid()) {
std::lock_guard<std::mutex> lockUnsyncedDirectViewProps(
unsyncedDirectViewPropsMutex_);
if (auto it = unsyncedDirectViewProps_.find(tag);
it != unsyncedDirectViewProps_.end()) {
return it->second;
}
}
return nullptr;
}
bool NativeAnimatedNodesManager::hasManagedProps() const noexcept {
{
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
if (!connectedAnimatedNodes_.empty()) {
return true;
}
}
if (!ReactNativeFeatureFlags::
overrideBySynchronousMountPropsAtMountingAndroid()) {
std::lock_guard<std::mutex> lock(unsyncedDirectViewPropsMutex_);
if (!unsyncedDirectViewProps_.empty()) {
return true;
}
}
return false;
}
void NativeAnimatedNodesManager::onManagedPropsRemoved(Tag tag) noexcept {
if (!ReactNativeFeatureFlags::
overrideBySynchronousMountPropsAtMountingAndroid()) {
std::lock_guard<std::mutex> lock(unsyncedDirectViewPropsMutex_);
if (auto iter = unsyncedDirectViewProps_.find(tag);
iter != unsyncedDirectViewProps_.end()) {
unsyncedDirectViewProps_.erase(iter);
}
}
}
bool NativeAnimatedNodesManager::isOnRenderThread() const noexcept {
return isOnRenderThread_;
}
#pragma mark - Listeners
void NativeAnimatedNodesManager::startListeningToAnimatedNodeValue(
Tag tag,
ValueListenerCallback&& callback) noexcept {
if (auto iter = animatedNodes_.find(tag); iter != animatedNodes_.end() &&
iter->second->type() == AnimatedNodeType::Value) {
static_cast<ValueAnimatedNode*>(iter->second.get())
->setValueListener(std::move(callback));
} else {
LOG(ERROR) << "startListeningToAnimatedNodeValue: Animated node [" << tag
<< "] does not exist, or is not a 'value' node";
}
}
void NativeAnimatedNodesManager::stopListeningToAnimatedNodeValue(
Tag tag) noexcept {
if (auto iter = animatedNodes_.find(tag); iter != animatedNodes_.end() &&
iter->second->type() == AnimatedNodeType::Value) {
static_cast<ValueAnimatedNode*>(iter->second.get())
->setValueListener(nullptr);
} else {
LOG(ERROR) << "stopListeningToAnimatedNodeValue: Animated node [" << tag
<< "] does not exist, or is not a 'value' node";
}
}
void NativeAnimatedNodesManager::schedulePropsCommit(
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
bool forceFabricCommit) noexcept {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
if (layoutStyleUpdated) {
mergeObjects(updateViewProps_[viewTag], props);
} else {
mergeObjects(updateViewPropsDirect_[viewTag], props);
}
return;
}
// When fabricCommitCallback_ & directManipulationCallback_ are both
// available, we commit layout props via Fabric and the other using direct
// manipulation. If only fabricCommitCallback_ is available, we commit all
// props using that; if only directManipulationCallback_ is available, we
// commit all except for layout props.
if (fabricCommitCallback_ != nullptr &&
(layoutStyleUpdated || forceFabricCommit ||
directManipulationCallback_ == nullptr)) {
mergeObjects(updateViewProps_[viewTag], props);
// Must call direct manipulation to set final values on components.
mergeObjects(updateViewPropsDirect_[viewTag], props);
} else if (!layoutStyleUpdated && directManipulationCallback_ != nullptr) {
mergeObjects(updateViewPropsDirect_[viewTag], props);
if (!ReactNativeFeatureFlags::
overrideBySynchronousMountPropsAtMountingAndroid()) {
std::lock_guard<std::mutex> lock(unsyncedDirectViewPropsMutex_);
mergeObjects(unsyncedDirectViewProps_[viewTag], props);
}
}
}
#ifdef RN_USE_ANIMATION_BACKEND
AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
return {};
}
TraceSection s(
"NativeAnimatedNodesManager::pullAnimations",
"numActiveAnimations",
activeAnimations_.size());
isOnRenderThread_ = true;
// Run operations scheduled from AnimatedModule
std::vector<UiTask> operations;
{
std::lock_guard<std::mutex> lock(uiTasksMutex_);
std::swap(operations_, operations);
}
for (auto& task : operations) {
task();
}
AnimationMutations mutations;
// Step through the animation loop
if (isAnimationUpdateNeeded()) {
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(
g_now().time_since_epoch())
.count();
auto timestamp = static_cast<double>(microseconds) / 1000.0;
bool containsChange = false;
AnimatedPropsBuilder propsBuilder;
{
// copied from onAnimationFrame
// Run all active animations
auto hasFinishedAnimations = false;
std::set<int> finishedAnimationValueNodes;
for (const auto& [_id, driver] : activeAnimations_) {
driver->runAnimationStep(timestamp);
if (driver->getIsComplete()) {
hasFinishedAnimations = true;
const auto shouldRemoveJsSync =
ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync() &&
!ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated();
if (shouldRemoveJsSync) {
finishedAnimationValueNodes.insert(driver->getAnimatedValueTag());
}
}
}
// Update all animated nodes
updateNodes(finishedAnimationValueNodes);
// remove finished animations
if (hasFinishedAnimations) {
std::vector<int> finishedAnimations;
for (const auto& [animationId, driver] : activeAnimations_) {
if (driver->getIsComplete()) {
if (getAnimatedNode<ValueAnimatedNode>(
driver->getAnimatedValueTag()) != nullptr) {
driver->stopAnimation();
}
finishedAnimations.emplace_back(animationId);
}
}
for (const auto& id : finishedAnimations) {
activeAnimations_.erase(id);
}
}
for (auto& [tag, props] : updateViewPropsDirect_) {
// TODO: also handle layout props (updateViewProps_). It is skipped for
// now, because the backend requires shadowNodeFamilies to be able to
// commit to the ShadowTree
propsBuilder.storeDynamic(props);
mutations.push_back(
AnimationMutation{tag, nullptr, propsBuilder.get()});
containsChange = true;
}
}
if (!containsChange) {
// The last animation tick didn't result in any changes to the UI.
// It is safe to assume any event animation that was in progress has
// completed.
// Step 1: gather all animations driven by events.
std::set<int> finishedAnimationValueNodes;
for (auto& [key, drivers] : eventDrivers_) {
for (auto& driver : drivers) {
finishedAnimationValueNodes.insert(driver->getAnimatedNodeTag());
if (auto node = getAnimatedNode<ValueAnimatedNode>(
driver->getAnimatedNodeTag())) {
updatedNodeTags_.insert(node->tag());
}
}
}
// Step 2: update all nodes that are connected to the finished animations.
updateNodes(finishedAnimationValueNodes);
isEventAnimationInProgress_ = false;
for (auto& [tag, props] : updateViewPropsDirect_) {
// TODO: handle layout props
propsBuilder.storeDynamic(props);
mutations.push_back(
AnimationMutation{tag, nullptr, propsBuilder.get()});
}
}
} else {
// There is no active animation. Stop the render callback.
stopRenderCallbackIfNeeded(false);
}
return mutations;
}
#endif
void NativeAnimatedNodesManager::onRender() {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
return;
}
TraceSection s(
"NativeAnimatedNodesManager::onRender",
"numActiveAnimations",
activeAnimations_.size());
if (frameRateListenerCallback_) {
frameRateListenerCallback_(true);
}
isOnRenderThread_ = true;
{
// Flush async created animated nodes
std::unordered_map<Tag, std::unique_ptr<AnimatedNode>>
animatedNodesCreatedAsync;
{
std::lock_guard<std::mutex> lock(animatedNodesCreatedAsyncMutex_);
std::swap(animatedNodesCreatedAsync, animatedNodesCreatedAsync_);
}
if (!animatedNodesCreatedAsync.empty()) {
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
for (auto& [tag, node] : animatedNodesCreatedAsync) {
animatedNodes_.insert({tag, std::move(node)});
updatedNodeTags_.insert(tag);
}
}
}
// Run operations scheduled from AnimatedModule
std::vector<UiTask> operations;
{
std::lock_guard<std::mutex> lock(uiTasksMutex_);
std::swap(operations_, operations);
}
for (auto& task : operations) {
task();
}
// Step through the animation loop
if (isAnimationUpdateNeeded()) {
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(
g_now().time_since_epoch())
.count();
auto timestamp = static_cast<double>(microseconds) / 1000.0;
auto containsChange = onAnimationFrame(timestamp);
if (!containsChange) {
// The last animation tick didn't result in any changes to the UI.
// It is safe to assume any event animation that was in progress has
// completed.
// Step 1: gather all animations driven by events.
std::set<int> finishedAnimationValueNodes;
for (auto& [key, drivers] : eventDrivers_) {
for (auto& driver : drivers) {
finishedAnimationValueNodes.insert(driver->getAnimatedNodeTag());
if (auto node = getAnimatedNode<ValueAnimatedNode>(
driver->getAnimatedNodeTag())) {
updatedNodeTags_.insert(node->tag());
}
}
}
// Step 2: update all nodes that are connected to the finished animations.
updateNodes(finishedAnimationValueNodes);
isEventAnimationInProgress_ = false;
// Step 3: commit the changes to the UI.
commitProps();
}
} else {
// There is no active animation. Stop the render callback.
stopRenderCallbackIfNeeded(false);
}
}
bool NativeAnimatedNodesManager::commitProps() {
bool containsChange =
!updateViewProps_.empty() || !updateViewPropsDirect_.empty();
if (fabricCommitCallback_ != nullptr) {
if (!updateViewProps_.empty()) {
fabricCommitCallback_(updateViewProps_);
}
}
updateViewProps_.clear();
if (directManipulationCallback_ != nullptr) {
for (const auto& [viewTag, props] : updateViewPropsDirect_) {
directManipulationCallback_(viewTag, folly::dynamic(props));
}
}
updateViewPropsDirect_.clear();
return containsChange;
}
} // namespace facebook::react