From bba451e4adb84a62a73d4af25e8e42689664afec Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Thu, 29 Feb 2024 17:16:01 +0900 Subject: [PATCH] Do not recreate SG::Animation when we call Clear + Redefine Clear behavior Let we make Clear don't destroy & re-create the SG::Animation. And also, we don't need to cache current loop count at event side. Let we ask it to scene graph side, Same as other values are doing now. Change-Id: I4b5353e7fb9ab1898ff4d22a8db78ccb3a19c460 Signed-off-by: Eunki, Hong --- automated-tests/src/dali/utc-Dali-Animation.cpp | 109 ++++++++++++++++++--- dali/internal/event/animation/animation-impl.cpp | 57 ++++++----- dali/internal/event/animation/animation-impl.h | 4 +- .../update/animation/scene-graph-animation.cpp | 41 ++++++-- .../update/animation/scene-graph-animation.h | 8 +- dali/internal/update/manager/update-manager.cpp | 15 ++- dali/internal/update/manager/update-manager.h | 20 ++++ 7 files changed, 204 insertions(+), 50 deletions(-) diff --git a/automated-tests/src/dali/utc-Dali-Animation.cpp b/automated-tests/src/dali/utc-Dali-Animation.cpp index 00847a6..489b906 100644 --- a/automated-tests/src/dali/utc-Dali-Animation.cpp +++ b/automated-tests/src/dali/utc-Dali-Animation.cpp @@ -3406,7 +3406,7 @@ int UtcDaliAnimationClearP(void) END_TEST; } -int UtcDaliAnimationEmptyAnimator(void) +int UtcDaliAnimationEmptyAnimatorAndLoopCount(void) { // Clear and play the empty animation, and get the state values. TestApplication application; @@ -3432,14 +3432,12 @@ int UtcDaliAnimationEmptyAnimator(void) DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); application.SendNotification(); application.Render(1500 /* 150% of loop. */); - application.SendNotification(); // Notification trigger. DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); DALI_TEST_EQUALS(animation.GetCurrentLoop(), 1, TEST_LOCATION); application.SendNotification(); application.Render(1400 /* 290% of loop. */); - application.SendNotification(); // Notification trigger. DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); DALI_TEST_EQUALS(animation.GetCurrentLoop(), 2, TEST_LOCATION); @@ -3451,49 +3449,105 @@ int UtcDaliAnimationEmptyAnimator(void) DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::STOPPED, TEST_LOCATION); DALI_TEST_EQUALS(animation.GetCurrentLoop(), 3, TEST_LOCATION); - // Check wether empty animation also call finished signal. + tet_printf("Check wether empty animation also call finished signal.\n"); finishCheck.CheckSignalReceived(); finishCheck.Reset(); animation.Play(); DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); - //DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); ///< TODO : We'd better change the policy of GetCurrentLoop result application.SendNotification(); + application.Render(0 /* 0% of loop. */); + + // LoopCount beome 0 again. + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); + application.Render(1500 /* 150% of loop. */); - application.SendNotification(); // Notification trigger. DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); DALI_TEST_EQUALS(animation.GetCurrentLoop(), 1, TEST_LOCATION); + animation.Pause(); + + application.SendNotification(); + application.Render(2500 /* 150% of loop. (Since it is paused) */); + + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PAUSED, TEST_LOCATION); + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 1, TEST_LOCATION); + + animation.Play(); + + application.SendNotification(); + application.Render(1000 /* 250% of loop. */); + + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 2, TEST_LOCATION); + animation.Clear(); DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::STOPPED, TEST_LOCATION); + + application.SendNotification(); + application.Render(500 + 100 /* 300% of loop + 10% over the loop. */); + application.SendNotification(); // Notification trigger. + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); + tet_printf("Check animation completed signal not recieved even if animation finished normally at this loop.\n"); + finishCheck.CheckSignalNotReceived(); + application.SendNotification(); - application.Render(1000); - application.Render(1000); + application.Render(1100); + application.Render(1100); application.Render(1100 /* Over the loop count */); application.SendNotification(); // Notification trigger. - // Check animation completed signal not recieved even of + tet_printf("Check animation completed signal not recieved even if animation finished normally.\n"); + finishCheck.CheckSignalNotReceived(); + + animation.Play(); + + application.SendNotification(); + application.Render(1500 /* 150% of loop. */); + + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 1, TEST_LOCATION); + + animation.Stop(); + animation.Clear(); + + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::STOPPED, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + application.SendNotification(); // Notification trigger. + + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); + + tet_printf("Check animation completed signal not recieved even if we call Stop forcibly.\n"); finishCheck.CheckSignalNotReceived(); - // Call clear again already cleared cases. animation.Clear(); DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::STOPPED, TEST_LOCATION); + + animation.Play(); + + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::PLAYING, TEST_LOCATION); DALI_TEST_EQUALS(animation.GetCurrentLoop(), 0, TEST_LOCATION); application.SendNotification(); - application.Render(1000); - application.Render(1000); + application.Render(1100); + application.Render(1100); application.Render(1100 /* Over the loop count */); application.SendNotification(); // Notification trigger. - // Check animation completed signal not recieved even of - finishCheck.CheckSignalNotReceived(); + DALI_TEST_EQUALS(animation.GetState(), Dali::Animation::STOPPED, TEST_LOCATION); + DALI_TEST_EQUALS(animation.GetCurrentLoop(), 3, TEST_LOCATION); + + tet_printf("Check animation completed signal recieved. (Since clear didn't disconnect complete signal)\n"); + finishCheck.CheckSignalReceived(); + finishCheck.Reset(); } catch(...) { @@ -14155,6 +14209,33 @@ void CheckPropertyValuesWhenCallingAnimationMethod(TestFunction functionToTest, DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get(), expectedValueTable[i].expectedGetPropertyValue, VECTOR3_EPSILON, TEST_LOCATION); DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get(), expectedValueTable[i].expectedGetPropertyValue, VECTOR3_EPSILON, TEST_LOCATION); + + // If we call Clear before, The animation didn't give any effort to actor now. Let we check it + if(functionToTest == TestFunction::CLEAR) + { + actor.SetProperty(Actor::Property::POSITION, originalPosition); + + DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get(), expectedValueTable[i].expectedGetPropertyValue, VECTOR3_EPSILON, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + + // Start the animation, which we already clear. + animation.Play(); + + DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + + application.SendNotification(); + application.Render(halfAnimationDuration); + + DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION); + } } } } // unnamed namespace diff --git a/dali/internal/event/animation/animation-impl.cpp b/dali/internal/event/animation/animation-impl.cpp index b9a4de4..ba2b1ab 100644 --- a/dali/internal/event/animation/animation-impl.cpp +++ b/dali/internal/event/animation/animation-impl.cpp @@ -264,7 +264,13 @@ int32_t Animation::GetLoopCount() int32_t Animation::GetCurrentLoop() { - return mCurrentLoop; + int32_t loopCount = 0; + if(mAnimation) // always exists in practice + { + loopCount = mAnimation->GetCurrentLoop(); + } + + return loopCount; } bool Animation::IsLooping() const @@ -304,6 +310,8 @@ Dali::Animation::EndAction Animation::GetDisconnectAction() const void Animation::Play() { + mPlayCalled = true; + // Update the current playlist mPlaylist.OnPlay(*this); @@ -321,6 +329,8 @@ void Animation::PlayFrom(float progress) { if(progress >= mPlayRange.x && progress <= mPlayRange.y) { + mPlayCalled = true; + // Update the current playlist mPlaylist.OnPlay(*this); @@ -337,6 +347,8 @@ void Animation::PlayFrom(float progress) void Animation::PlayAfter(float delaySeconds) { + mPlayCalled = true; + // The negative delay means play immediately. delaySeconds = std::max(0.f, delaySeconds); @@ -357,13 +369,16 @@ void Animation::PlayAfter(float delaySeconds) void Animation::Pause() { - mState = Dali::Animation::PAUSED; + if(mState != Dali::Animation::PAUSED) + { + mState = Dali::Animation::PAUSED; - // mAnimation is being used in a separate thread; queue a Pause message - PauseAnimationMessage(mEventThreadServices, *mAnimation); + // mAnimation is being used in a separate thread; queue a Pause message + PauseAnimationMessage(mEventThreadServices, *mAnimation); - // Notify the objects with the _paused_, i.e. current values - NotifyObjects(Notify::FORCE_CURRENT_VALUE); + // Notify the objects with the _paused_, i.e. current values + NotifyObjects(Notify::FORCE_CURRENT_VALUE); + } } Dali::Animation::State Animation::GetState() const @@ -373,15 +388,18 @@ Dali::Animation::State Animation::GetState() const void Animation::Stop() { - mState = Dali::Animation::STOPPED; + if(mState != Dali::Animation::STOPPED) + { + mState = Dali::Animation::STOPPED; - // mAnimation is being used in a separate thread; queue a Stop message - StopAnimationMessage(mEventThreadServices.GetUpdateManager(), *mAnimation); + // mAnimation is being used in a separate thread; queue a Stop message + StopAnimationMessage(mEventThreadServices.GetUpdateManager(), *mAnimation); - // Only notify the objects with the _stopped_, i.e. current values if the end action is set to BAKE - if(mEndAction == EndAction::BAKE) - { - NotifyObjects(Notify::USE_CURRENT_VALUE); + // Only notify the objects with the _stopped_, i.e. current values if the end action is set to BAKE + if(mEndAction == EndAction::BAKE) + { + NotifyObjects(Notify::USE_CURRENT_VALUE); + } } } @@ -389,8 +407,7 @@ void Animation::Clear() { DALI_ASSERT_DEBUG(mAnimation); - // Recreate scene-object only if animation play now, or connector connected at least 1 times. - if(mConnectors.Count() == 0u && mState == Dali::Animation::STOPPED) + if(mConnectors.Empty() && mState == Dali::Animation::STOPPED && !mPlayCalled) { // Animation is empty. Fast-out return; @@ -409,14 +426,13 @@ void Animation::Clear() mConnectorTargetValues.clear(); mConnectorTargetValuesSortRequired = false; - // Replace the old scene-object with a new one - DestroySceneObject(); - CreateSceneObject(); + // mAnimation is being used in a separate thread; queue a Clear message + ClearAnimationMessage(mEventThreadServices.GetUpdateManager(), *mAnimation); // Reset the notification count and relative values, since the new scene-object has never been played mNotificationCount = 0; - mCurrentLoop = 0; mState = Dali::Animation::STOPPED; + mPlayCalled = false; // Update the current playlist mPlaylist.OnClear(*this); @@ -815,9 +831,6 @@ bool Animation::HasFinished() bool hasFinished(false); const int32_t playedCount(mAnimation->GetPlayedCount()); - // If the play count has been incremented, then another notification is required - mCurrentLoop = mAnimation->GetCurrentLoop(); - if(playedCount > mNotificationCount) { // Note that only one signal is emitted, if the animation has been played repeatedly diff --git a/dali/internal/event/animation/animation-impl.h b/dali/internal/event/animation/animation-impl.h index 73972b0..dd91ff8 100644 --- a/dali/internal/event/animation/animation-impl.h +++ b/dali/internal/event/animation/animation-impl.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_ANIMATION_H /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -569,7 +569,6 @@ private: float mSpeedFactor{1.0f}; int32_t mNotificationCount{0}; ///< Keep track of how many Finished signals have been emitted. int32_t mLoopCount{1}; - int32_t mCurrentLoop{0}; float mProgressReachedMarker{0.0f}; float mDelaySeconds{0.0f}; EndAction mEndAction; @@ -577,6 +576,7 @@ private: Dali::Animation::State mState{Dali::Animation::STOPPED}; bool mAutoReverseEnabled{false}; ///< Flag to identify that the looping mode is auto reverse. bool mConnectorTargetValuesSortRequired{false}; ///< Flag to whether we need to sort mConnectorTargetValues or not + bool mPlayCalled{false}; ///< Flag to whether we call Play at least 1 time after create, or clear. }; } // namespace Internal diff --git a/dali/internal/update/animation/scene-graph-animation.cpp b/dali/internal/update/animation/scene-graph-animation.cpp index 75551c6..8530da2 100644 --- a/dali/internal/update/animation/scene-graph-animation.cpp +++ b/dali/internal/update/animation/scene-graph-animation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -167,6 +167,11 @@ void Animation::Play() mAnimatorSortRequired = false; } + // Let we don't change current loop value if the state was paused. + if(mState != Paused) + { + mCurrentLoop = 0; + } mState = Playing; if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds) @@ -175,8 +180,6 @@ void Animation::Play() } SetAnimatorsActive(true); - - mCurrentLoop = 0; } void Animation::PlayFrom(float progress) @@ -186,11 +189,14 @@ void Animation::PlayFrom(float progress) if(mState != Playing) { mElapsedSeconds = progress * mDurationSeconds; - mState = Playing; + // Let we don't change current loop value if the state was paused. + if(mState != Paused) + { + mCurrentLoop = 0; + } + mState = Playing; SetAnimatorsActive(true); - - mCurrentLoop = 0; } } @@ -199,7 +205,12 @@ void Animation::PlayAfter(float delaySeconds) if(mState != Playing) { mDelaySeconds = delaySeconds; - mState = Playing; + // Let we don't change current loop value if the state was paused. + if(mState != Paused) + { + mCurrentLoop = 0; + } + mState = Playing; if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds) { @@ -207,8 +218,6 @@ void Animation::PlayAfter(float delaySeconds) } SetAnimatorsActive(true); - - mCurrentLoop = 0; } } @@ -276,6 +285,20 @@ bool Animation::Stop(BufferIndex bufferIndex) return animationFinished; } +void Animation::Clear(BufferIndex bufferIndex) +{ + // Stop animation immediatly. + Stop(bufferIndex); + + // Remove all animator. + mAnimators.Clear(); + mAnimatorSortRequired = false; + + // Reset animation state values. + mPlayedCount = 0; + mCurrentLoop = 0; +} + void Animation::OnDestroy(BufferIndex bufferIndex) { if(mState == Playing || mState == Paused) diff --git a/dali/internal/update/animation/scene-graph-animation.h b/dali/internal/update/animation/scene-graph-animation.h index a1bb347..40cf2a3 100644 --- a/dali/internal/update/animation/scene-graph-animation.h +++ b/dali/internal/update/animation/scene-graph-animation.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_SCENE_GRAPH_ANIMATION_H /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -232,6 +232,12 @@ public: bool Stop(BufferIndex bufferIndex); /** + * Clear the animation. It will clear all animator, and make this animation never played before. + * @param[in] bufferIndex The buffer to update when mEndAction == Bake. + */ + void Clear(BufferIndex bufferIndex); + + /** * Called shortly before the animation is destroyed. * @param[in] bufferIndex The buffer to update when mEndAction == Bake. */ diff --git a/dali/internal/update/manager/update-manager.cpp b/dali/internal/update/manager/update-manager.cpp index c45d9af..df4a2d2 100644 --- a/dali/internal/update/manager/update-manager.cpp +++ b/dali/internal/update/manager/update-manager.cpp @@ -596,6 +596,17 @@ void UpdateManager::StopAnimation(Animation* animation) mImpl->animationFinishedDuringUpdate = mImpl->animationFinishedDuringUpdate || animationFinished; } +void UpdateManager::ClearAnimation(Animation* animation) +{ + DALI_ASSERT_DEBUG(animation && "NULL animation called to clear"); + + animation->Clear(mSceneGraphBuffers.GetUpdateBufferIndex()); + + // We should remove all notify lists what we requests before clear. + // TODO : Could we do this more faster? + Dali::EraseIf(mImpl->notifyRequiredAnimations, [&animation](const NotifierInterface::NotifyId& key) { return key == animation->GetNotifyId(); }); +} + void UpdateManager::RemoveAnimation(Animation* animation) { DALI_ASSERT_DEBUG(animation && "NULL animation called to remove"); @@ -841,8 +852,8 @@ bool UpdateManager::Animate(BufferIndex bufferIndex, float elapsedSeconds) mImpl->animationFinishedDuringUpdate = mImpl->animationFinishedDuringUpdate || finished; animationLooped = animationLooped || looped; - // queue the notification on finished or stoped or looped (to update loop count) - if(finished || looped) + // queue the notification on finished or stoped + if(finished) { mImpl->notifyRequiredAnimations.PushBack(animation->GetNotifyId()); } diff --git a/dali/internal/update/manager/update-manager.h b/dali/internal/update/manager/update-manager.h index a5702a0..914f3af 100644 --- a/dali/internal/update/manager/update-manager.h +++ b/dali/internal/update/manager/update-manager.h @@ -235,6 +235,12 @@ public: void StopAnimation(Animation* animation); /** + * Clear an animation. + * @param[in] animation The animation to clear. + */ + void ClearAnimation(Animation* animation); + + /** * Remove an animation. * @param[in] animation The animation to remove. */ @@ -978,6 +984,20 @@ inline void StopAnimationMessage(UpdateManager& manager, const Animation& constA new(slot) LocalType(&manager, &UpdateManager::StopAnimation, &animation); } +inline void ClearAnimationMessage(UpdateManager& manager, const Animation& constAnimation) +{ + // The scene-graph thread owns this object so it can safely edit it. + Animation& animation = const_cast(constAnimation); + + using LocalType = MessageValue1; + + // Reserve some memory inside the message queue + uint32_t* slot = manager.ReserveMessageSlot(sizeof(LocalType)); + + // Construct message in the message queue memory; note that delete should not be called on the return value + new(slot) LocalType(&manager, &UpdateManager::ClearAnimation, &animation); +} + inline void RemoveAnimationMessage(UpdateManager& manager, const Animation& constAnimation) { // The scene-graph thread owns this object so it can safely edit it. -- 2.7.4