[Tizen] Do not recreate SG::Animation when we call Clear + Redefine Clear behavior 24/307024/2
authorEunki, Hong <eunkiki.hong@samsung.com>
Thu, 29 Feb 2024 08:16:01 +0000 (17:16 +0900)
committerEunki Hong <eunkiki.hong@samsung.com>
Tue, 2 Apr 2024 16:21:52 +0000 (01:21 +0900)
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 <eunkiki.hong@samsung.com>
automated-tests/src/dali/utc-Dali-Animation.cpp
dali/internal/event/animation/animation-impl.cpp
dali/internal/event/animation/animation-impl.h
dali/internal/update/animation/scene-graph-animation.cpp
dali/internal/update/animation/scene-graph-animation.h
dali/internal/update/manager/update-manager.cpp
dali/internal/update/manager/update-manager.h

index 00847a6..489b906 100644 (file)
@@ -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<Vector3>(), expectedValueTable[i].expectedGetPropertyValue, VECTOR3_EPSILON, TEST_LOCATION);
     DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>(), 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<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+      DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>(), expectedValueTable[i].expectedGetPropertyValue, VECTOR3_EPSILON, TEST_LOCATION);
+
+      application.SendNotification();
+      application.Render();
+
+      DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+      DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+
+      // Start the animation, which we already clear.
+      animation.Play();
+
+      DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+      DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+
+      application.SendNotification();
+      application.Render(halfAnimationDuration);
+
+      DALI_TEST_EQUALS(actor.GetProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+      DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>(), originalPosition, VECTOR3_EPSILON, TEST_LOCATION);
+    }
   }
 }
 } // unnamed namespace
index b9a4de4..ba2b1ab 100644 (file)
@@ -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
index 73972b0..dd91ff8 100644 (file)
@@ -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
index b35206e..8862e78 100644 (file)
@@ -169,6 +169,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)
@@ -177,8 +182,6 @@ void Animation::Play()
   }
 
   SetAnimatorsActive(true);
-
-  mCurrentLoop = 0;
 }
 
 void Animation::PlayFrom(float progress)
@@ -188,11 +191,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;
   }
 }
 
@@ -201,7 +207,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)
     {
@@ -209,8 +220,6 @@ void Animation::PlayAfter(float delaySeconds)
     }
 
     SetAnimatorsActive(true);
-
-    mCurrentLoop = 0;
   }
 }
 
@@ -280,6 +289,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)
index a1bb347..40cf2a3 100644 (file)
@@ -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.
    */
index ed899f4..b37e236 100644 (file)
@@ -587,6 +587,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");
@@ -871,8 +882,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());
     }
index 8499642..1e6231f 100644 (file)
@@ -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.
    */
@@ -970,6 +976,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<Animation&>(constAnimation);
+
+  using LocalType = MessageValue1<UpdateManager, Animation*>;
+
+  // 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.