[dali_1.2.40] Merge branch 'devel/master'
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
index 4449e7f..3f33f3c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
 #include <cmath> // fmod
 
 // INTERNAL INCLUDES
+#include <dali/internal/common/memory-pool-object-allocator.h>
 #include <dali/internal/render/common/performance-monitor.h>
 
+namespace //Unnamed namespace
+{
+//Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
+Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
+
+inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
+{
+  if( elapsed > playRangeSeconds.y )
+  {
+    elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
+  }
+  else if( elapsed < playRangeSeconds.x )
+  {
+    elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
+  }
+}
+
+/// Compares the end times of the animators and if the end time is less, then it is moved earlier in the list. If end times are the same, then no change.
+bool CompareAnimatorEndTimes( const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs )
+{
+  return ( ( lhs->GetInitialDelay() + lhs->GetDuration() ) < ( rhs->GetInitialDelay() + rhs->GetDuration() ) );
+}
+
+} // unnamed namespace
+
 namespace Dali
 {
 
@@ -33,20 +59,21 @@ namespace Internal
 namespace SceneGraph
 {
 
-float DefaultAlphaFunc(float progress)
+Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, EndAction endAction, EndAction disconnectAction )
 {
-  return progress; // linear
+  return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
 }
 
-Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, bool isLooping, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
+Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
 : mDurationSeconds(durationSeconds),
   mSpeedFactor( speedFactor ),
-  mLooping(isLooping),
   mEndAction(endAction),
   mDisconnectAction(disconnectAction),
   mState(Stopped),
   mElapsedSeconds(playRange.x*mDurationSeconds),
-  mPlayCount(0),
+  mPlayedCount(0),
+  mLoopCount(loopCount),
+  mCurrentLoop(0),
   mPlayRange( playRange )
 {
 }
@@ -55,16 +82,20 @@ Animation::~Animation()
 {
 }
 
-void Animation::SetDuration(float durationSeconds)
+void Animation::operator delete( void* ptr )
 {
-  DALI_ASSERT_DEBUG(durationSeconds > 0.0f);
+  gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
+}
 
+void Animation::SetDuration(float durationSeconds)
+{
   mDurationSeconds = durationSeconds;
 }
 
-void Animation::SetLooping(bool looping)
+void Animation::SetLoopCount(int loopCount)
 {
-  mLooping = looping;
+  mLoopCount = loopCount;
+  mCurrentLoop = 0;
 }
 
 void Animation::SetEndAction(Dali::Animation::EndAction action)
@@ -89,12 +120,27 @@ void Animation::SetPlayRange( const Vector2& range )
 {
   mPlayRange = range;
 
-  //Make sure mElapsedSeconds is within the new range
-  mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
+  // Make sure mElapsedSeconds is within the new range
+
+  if( mState == Stopped )
+  {
+    // Ensure that the animation starts at the right place
+    mElapsedSeconds = mPlayRange.x * mDurationSeconds;
+  }
+  else
+  {
+    // If already past the end of the range, but before end of duration, then clamp will
+    // ensure that the animation stops on the next update.
+    // If not yet at the start of the range, clamping will jump to the start
+    mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
+  }
 }
 
 void Animation::Play()
 {
+  // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
+  std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
+
   mState = Playing;
 
   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
@@ -103,11 +149,14 @@ void Animation::Play()
   }
 
   SetAnimatorsActive( true );
+
+  mCurrentLoop = 0;
 }
 
 void Animation::PlayFrom( float progress )
 {
-  //If the animation is already playing this has no effect
+  // If the animation is already playing this has no effect
+  // Progress is guaranteed to be in range.
   if( mState != Playing )
   {
     mElapsedSeconds = progress * mDurationSeconds;
@@ -170,7 +219,8 @@ bool Animation::Stop(BufferIndex bufferIndex)
     }
 
     // The animation has now been played to completion
-    ++mPlayCount;
+    ++mPlayedCount;
+    mCurrentLoop = 0;
   }
 
   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
@@ -200,16 +250,21 @@ void Animation::OnDestroy(BufferIndex bufferIndex)
 
 void Animation::AddAnimator( AnimatorBase* animator )
 {
+  animator->ConnectToSceneGraph();
   animator->SetDisconnectAction( mDisconnectAction );
+
   mAnimators.PushBack( animator );
 }
 
-bool Animation::Update(BufferIndex bufferIndex, float elapsedSeconds)
+void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished )
 {
+  looped = false;
+  finished = false;
+
   if (mState == Stopped || mState == Destroyed)
   {
     // Short circuit when animation isn't running
-    return false;
+    return;
   }
 
   // The animation must still be applied when Paused/Stopping
@@ -219,40 +274,65 @@ bool Animation::Update(BufferIndex bufferIndex, float elapsedSeconds)
   }
 
   Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
-  if (mLooping)
+
+  if( 0 == mLoopCount )
   {
-    if (mElapsedSeconds > playRangeSeconds.y )
-    {
-      mElapsedSeconds = playRangeSeconds.x + fmod(mElapsedSeconds, playRangeSeconds.y);
-    }
-    else if( mElapsedSeconds < playRangeSeconds.x )
+    // loop forever
+    WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
+
+    UpdateAnimators(bufferIndex, false, false);
+
+    // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
+  }
+  else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
+  {
+    // looping
+    looped =  (mState == Playing                                                 &&
+               (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
+                ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
+
+    WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
+
+    UpdateAnimators(bufferIndex, false, false);
+
+    if(looped)
     {
-      mElapsedSeconds = playRangeSeconds.y - fmod(mElapsedSeconds, playRangeSeconds.y);
+      ++mCurrentLoop;
+      // don't increment mPlayedCount until the finished final loop
     }
   }
+  else
+  {
+    // playing once (and last mCurrentLoop)
+    finished = (mState == Playing                                                 &&
+                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
+                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
 
-  const bool animationFinished(mState == Playing                                                &&
-                              (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
-                               ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x ))
-                              );
+    // update with bake if finished
+    UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished);
 
-  UpdateAnimators(bufferIndex, animationFinished && (mEndAction != Dali::Animation::Discard), animationFinished);
+    if(finished)
+    {
+      // The animation has now been played to completion
+      ++mPlayedCount;
 
-  if (animationFinished)
-  {
-    // The animation has now been played to completion
-    ++mPlayCount;
+      // loop iterations come to this else branch for their final iterations
+      if( mCurrentLoop < mLoopCount)
+      {
+        ++mCurrentLoop;
+        DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
+      }
 
-    mElapsedSeconds = playRangeSeconds.x;
-    mState = Stopped;
+      mElapsedSeconds = playRangeSeconds.x;
+      mState = Stopped;
+    }
   }
-
-  return animationFinished;
 }
 
 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
 {
-  float elapsedSecondsClamped = Clamp( mElapsedSeconds, mPlayRange.x * mDurationSeconds,mPlayRange.y * mDurationSeconds );
+  const Vector2 playRange( mPlayRange * mDurationSeconds );
+  float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
 
   //Loop through all animators
   bool applied(true);
@@ -269,8 +349,8 @@ void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animat
     {
       if( animator->IsEnabled() )
       {
-        const float initialDelay(animator->GetInitialDelay());
-        if (elapsedSecondsClamped >= initialDelay || mSpeedFactor < 0.0f )
+        const float initialDelay( animator->GetInitialDelay() );
+        if( elapsedSecondsClamped >= initialDelay )
         {
           // Calculate a progress specific to each individual animator
           float progress(1.0f);