Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fixes issue with restarting animations",
"packageName": "react-native-windows",
"email": "erozell@outlook.com",
"dependentChangeType": "patch"
}
41 changes: 24 additions & 17 deletions vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ AnimationDriver::~AnimationDriver() {
}

void AnimationDriver::StartAnimation() {
m_started = true;
const auto [animation, scopedBatch] = MakeAnimation(m_config);
if (auto const animatedValue = GetAnimatedValue()) {
animatedValue->PropertySet().StartAnimation(ValueAnimatedNode::s_valueName, animation);
Expand All @@ -51,15 +52,23 @@ void AnimationDriver::StartAnimation() {

m_scopedBatchCompletedToken = scopedBatch.Completed(
[weakSelf = weak_from_this(), weakManager = m_manager, id = m_id, tag = m_animatedValueTag](auto sender, auto) {
if (const auto strongSelf = weakSelf.lock()) {
strongSelf->DoCallback(true);
}

const auto strongSelf = weakSelf.lock();
const auto ignoreCompletedHandlers = strongSelf && strongSelf->m_ignoreCompletedHandlers;
if (auto manager = weakManager.lock()) {
if (auto const animatedValue = manager->GetValueAnimatedNode(tag)) {
animatedValue->RemoveActiveAnimation(id);
// If the animation was stopped for a tracking node, do not clean up the active animation state.
if (!ignoreCompletedHandlers) {
if (const auto animatedValue = manager->GetValueAnimatedNode(tag)) {
animatedValue->RemoveActiveAnimation(id);
}
manager->RemoveActiveAnimation(id);
}
manager->RemoveActiveAnimation(id);

// Always update the stopped animations in case any animations are deferred for the same value.
manager->RemoveStoppedAnimation(id, manager);
}

if (strongSelf && !ignoreCompletedHandlers) {
strongSelf->DoCallback(!strongSelf->m_stopped);
}
});

Expand All @@ -68,17 +77,15 @@ void AnimationDriver::StartAnimation() {
}

void AnimationDriver::StopAnimation(bool ignoreCompletedHandlers) {
if (const auto animatedValue = GetAnimatedValue()) {
if (!m_started) {
// The animation may have been deferred and never started. In this case,
// we will never get a scoped batch completion, so we need to fire the
// callback synchronously.
DoCallback(false);
} else if (const auto animatedValue = GetAnimatedValue()) {
animatedValue->PropertySet().StopAnimation(ValueAnimatedNode::s_valueName);
if (!ignoreCompletedHandlers) {
animatedValue->RemoveActiveAnimation(m_id);

if (m_scopedBatch) {
DoCallback(false);
m_scopedBatch.Completed(m_scopedBatchCompletedToken);
m_scopedBatch = nullptr;
}
}
m_stopped = true;
m_ignoreCompletedHandlers = ignoreCompletedHandlers;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,8 @@ class AnimationDriver : public std::enable_shared_from_this<AnimationDriver> {
// auto revoker for scopedBatch.Completed is broken, tracked by internal bug
// #22399779
winrt::event_token m_scopedBatchCompletedToken{};
bool m_started{false};
bool m_stopped{false};
bool m_ignoreCompletedHandlers{false};
};
} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,37 @@ void NativeAnimatedNodeManager::DisconnectAnimatedNode(int64_t parentNodeTag, in
}
}

void NativeAnimatedNodeManager::StopAnimation(int64_t animationId) {
void NativeAnimatedNodeManager::StopAnimation(int64_t animationId, bool isTrackingAnimation) {
if (m_activeAnimations.count(animationId)) {
if (const auto animation = m_activeAnimations.at(animationId).get()) {
animation->StopAnimation();
if (const auto animation = m_activeAnimations.at(animationId)) {
animation->StopAnimation(isTrackingAnimation);

// Insert the animation into the pending completion set to ensure it is
// not destroyed before the callback occurs. It's safe to assume the
// scoped batch completion callback has not run, since if it had, the
// animation would have been removed from the set of active animations.
m_pendingCompletionAnimations.insert({animationId, animation});

const auto nodeTag = animation->AnimatedValueTag();
if (nodeTag != -1) {
const auto deferredAnimation = m_deferredAnimationForValues.find(nodeTag);
if (deferredAnimation != m_deferredAnimationForValues.end() && deferredAnimation->second == animationId) {
// If the animation is deferred, just remove the deferred animation
// entry as two animations cannot animate the same value concurrently.
m_deferredAnimationForValues.erase(nodeTag);
} else {
// Since only one animation can be active at a time, there shouldn't
// be any stopped animations for the value node if the animation has
// not been deferred.
assert(!m_valuesWithStoppedAnimation.count(nodeTag));
// In this case, add the value tag to the set of values with stopped
// animations. This is used to optimize the lookup when determining
// if an animation needs to be deferred (rather than iterating over
// the map of pending completion animations).
m_valuesWithStoppedAnimation.insert(nodeTag);
}
}

m_activeAnimations.erase(animationId);
}
}
Expand Down Expand Up @@ -241,13 +268,17 @@ void NativeAnimatedNodeManager::StartAnimatingNode(
break;
}

// If the animated value node has any stopped animations, defer start until
// all stopped animations fire completion callback and have latest values.
if (m_activeAnimations.count(animationId)) {
m_activeAnimations.at(animationId)->StartAnimation();

for (auto const &trackingAndLead : m_trackingAndLeadNodeTags) {
if (std::get<1>(trackingAndLead) == animatedNodeTag) {
RestartTrackingAnimatedNode(std::get<0>(trackingAndLead), std::get<1>(trackingAndLead), manager);
}
if (m_valuesWithStoppedAnimation.count(animatedNodeTag)) {
// Since only one animation can be active per value at a time, there will
// not be any other deferred animations for the value node.
assert(!m_deferredAnimationForValues.count(animatedNodeTag));
// Add the animation to the deferred animation map for the value tag.
m_deferredAnimationForValues.insert({animatedNodeTag, animationId});
} else {
StartAnimationAndTrackingNodes(animationId, animatedNodeTag, manager);
}
}
}
Expand Down Expand Up @@ -406,4 +437,43 @@ TrackingAnimatedNode *NativeAnimatedNodeManager::GetTrackingAnimatedNode(int64_t
void NativeAnimatedNodeManager::RemoveActiveAnimation(int64_t tag) {
m_activeAnimations.erase(tag);
}

void NativeAnimatedNodeManager::RemoveStoppedAnimation(
int64_t tag,
const std::shared_ptr<NativeAnimatedNodeManager> &manager) {
if (m_pendingCompletionAnimations.count(tag)) {
// Remove stopped animation for value node entry
const auto animation = m_pendingCompletionAnimations.at(tag);
const auto nodeTag = animation->AnimatedValueTag();
// If the animation was stopped, attempt to start deferred animations.
if (m_valuesWithStoppedAnimation.erase(nodeTag)) {
StartDeferredAnimationsForValueNode(nodeTag, manager);
}
m_pendingCompletionAnimations.erase(tag);
}
}

void NativeAnimatedNodeManager::StartDeferredAnimationsForValueNode(
int64_t tag,
const std::shared_ptr<NativeAnimatedNodeManager> &manager) {
if (m_deferredAnimationForValues.count(tag)) {
const auto deferredAnimationTag = m_deferredAnimationForValues.at(tag);
StartAnimationAndTrackingNodes(deferredAnimationTag, tag, manager);
m_deferredAnimationForValues.erase(tag);
}
}

void NativeAnimatedNodeManager::StartAnimationAndTrackingNodes(
int64_t tag,
int64_t nodeTag,
const std::shared_ptr<NativeAnimatedNodeManager> &manager) {
if (m_activeAnimations.count(tag)) {
m_activeAnimations.at(tag)->StartAnimation();
for (auto const &trackingAndLead : m_trackingAndLeadNodeTags) {
if (std::get<1>(trackingAndLead) == nodeTag) {
RestartTrackingAnimatedNode(std::get<0>(trackingAndLead), std::get<1>(trackingAndLead), manager);
}
}
}
}
} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class NativeAnimatedNodeManager {
void DisconnectAnimatedNodeToView(int64_t propsNodeTag, int64_t viewTag);
void ConnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag);
void DisconnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag);
void StopAnimation(int64_t animationId);
void StopAnimation(int64_t animationId, bool isTrackingAnimation = false);
void RestartTrackingAnimatedNode(
int64_t animationId,
int64_t animatedToValueTag,
Expand Down Expand Up @@ -88,7 +88,16 @@ class NativeAnimatedNodeManager {
StyleAnimatedNode *GetStyleAnimatedNode(int64_t tag);
TransformAnimatedNode *GetTransformAnimatedNode(int64_t tag);
TrackingAnimatedNode *GetTrackingAnimatedNode(int64_t tag);

void RemoveActiveAnimation(int64_t tag);
void RemoveStoppedAnimation(int64_t tag, const std::shared_ptr<NativeAnimatedNodeManager> &manager);
void StartDeferredAnimationsForValueNode(
int64_t valueNodeTag,
const std::shared_ptr<NativeAnimatedNodeManager> &manager);
void StartAnimationAndTrackingNodes(
int64_t tag,
int64_t nodeTag,
const std::shared_ptr<NativeAnimatedNodeManager> &manager);

private:
std::unordered_map<int64_t, std::unique_ptr<ValueAnimatedNode>> m_valueNodes{};
Expand All @@ -99,6 +108,9 @@ class NativeAnimatedNodeManager {
std::unordered_map<std::tuple<int64_t, std::string>, std::vector<std::unique_ptr<EventAnimationDriver>>>
m_eventDrivers{};
std::unordered_map<int64_t, std::shared_ptr<AnimationDriver>> m_activeAnimations{};
std::unordered_map<int64_t, std::shared_ptr<AnimationDriver>> m_pendingCompletionAnimations{};
std::unordered_set<int64_t> m_valuesWithStoppedAnimation{};
std::unordered_map<int64_t, int64_t> m_deferredAnimationForValues{};
std::vector<std::tuple<int64_t, int64_t>> m_trackingAndLeadNodeTags{};
std::vector<int64_t> m_delayedPropsNodes{};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ void TrackingAnimatedNode::Update() {
void TrackingAnimatedNode::StartAnimation() {
if (auto const strongManager = m_manager.lock()) {
if (auto const toValueNode = strongManager->GetValueAnimatedNode(m_toValueId)) {
// 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.
strongManager->StopAnimation(m_animationId, true);
toValueNode->AddActiveTrackingNode(m_tag);
m_animationConfig.insert(static_cast<folly::StringPiece>(s_toValueIdName), toValueNode->Value());
strongManager->StartTrackingAnimatedNode(
Expand Down