(Animation) Fix assert when animation is resumed using PlayAfter after all loops...
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/update/animation/scene-graph-animation.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath> // fmod
23
24 // INTERNAL INCLUDES
25 #include <dali/internal/common/memory-pool-object-allocator.h>
26 #include <dali/internal/render/common/performance-monitor.h>
27 #include <dali/public-api/math/math-utils.h>
28 namespace //Unnamed namespace
29 {
30 //Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
31 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
32
33 inline void WrapInPlayRange( float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
34 {
35   if( elapsed > playRangeEndSeconds )
36   {
37     elapsed = playRangeStartSeconds + fmodf( ( elapsed - playRangeStartSeconds ), ( playRangeEndSeconds - playRangeStartSeconds ) );
38   }
39   else if( elapsed < playRangeStartSeconds )
40   {
41     elapsed = playRangeEndSeconds - fmodf( ( playRangeStartSeconds - elapsed ), ( playRangeEndSeconds - playRangeStartSeconds ) );
42   }
43 }
44
45 /// 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.
46 bool CompareAnimatorEndTimes( const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs )
47 {
48   return ( ( lhs->GetIntervalDelay() + lhs->GetDuration() ) < ( rhs->GetIntervalDelay() + rhs->GetDuration() ) );
49 }
50
51 } // unnamed namespace
52
53 namespace Dali
54 {
55
56 namespace Internal
57 {
58
59 namespace SceneGraph
60 {
61
62 Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction )
63 {
64   return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
65 }
66
67 Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
68 : mPlayRange( playRange ),
69   mDurationSeconds( durationSeconds ),
70   mDelaySeconds( 0.0f ),
71   mElapsedSeconds( playRange.x*mDurationSeconds ),
72   mSpeedFactor( speedFactor ),
73   mProgressMarker( 0.0f ),
74   mPlayedCount( 0 ),
75   mLoopCount(loopCount),
76   mCurrentLoop(0),
77   mEndAction(endAction),
78   mDisconnectAction(disconnectAction),
79   mState(Stopped),
80   mProgressReachedSignalRequired( false ),
81   mAutoReverseEnabled( false )
82 {
83 }
84
85 Animation::~Animation()
86 {
87 }
88
89 void Animation::operator delete( void* ptr )
90 {
91   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
92 }
93
94 void Animation::SetDuration(float durationSeconds)
95 {
96   mDurationSeconds = durationSeconds;
97 }
98
99 void Animation::SetProgressNotification( float progress )
100 {
101   mProgressMarker = progress;
102   if ( mProgressMarker > 0.0f )
103   {
104     mProgressReachedSignalRequired = true;
105   }
106 }
107
108 void Animation::SetLoopCount(int32_t loopCount)
109 {
110   mLoopCount = loopCount;
111   mCurrentLoop = 0;
112 }
113
114 void Animation::SetEndAction(Dali::Animation::EndAction action)
115 {
116   mEndAction = action;
117 }
118
119 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
120 {
121   if ( mDisconnectAction != action )
122   {
123     mDisconnectAction = action;
124
125     for ( auto&& item : mAnimators )
126     {
127       item->SetDisconnectAction( action );
128     }
129   }
130 }
131
132 void Animation::SetPlayRange( const Vector2& range )
133 {
134   mPlayRange = range;
135
136   // Make sure mElapsedSeconds is within the new range
137
138   if( mState == Stopped )
139   {
140     // Ensure that the animation starts at the right place
141     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
142   }
143   else
144   {
145     // If already past the end of the range, but before end of duration, then clamp will
146     // ensure that the animation stops on the next update.
147     // If not yet at the start of the range, clamping will jump to the start
148     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
149   }
150 }
151
152 void Animation::Play()
153 {
154   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
155   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
156
157   mState = Playing;
158
159   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
160   {
161     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
162   }
163
164   SetAnimatorsActive( true );
165
166   mCurrentLoop = 0;
167 }
168
169 void Animation::PlayFrom( float progress )
170 {
171   // If the animation is already playing this has no effect
172   // Progress is guaranteed to be in range.
173   if( mState != Playing )
174   {
175     mElapsedSeconds = progress * mDurationSeconds;
176     mState = Playing;
177
178     SetAnimatorsActive( true );
179
180     mCurrentLoop = 0;
181   }
182 }
183
184 void Animation::PlayAfter( float delaySeconds )
185 {
186   if( mState != Playing )
187   {
188     mDelaySeconds = delaySeconds;
189     mState = Playing;
190
191     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
192     {
193       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
194     }
195
196     SetAnimatorsActive( true );
197
198     mCurrentLoop = 0;
199   }
200 }
201
202 void Animation::Pause()
203 {
204   if (mState == Playing)
205   {
206     mState = Paused;
207   }
208 }
209
210 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
211 {
212   if( action == Dali::Animation::BakeFinal )
213   {
214     if( mSpeedFactor > 0.0f )
215     {
216       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
217     }
218     else
219     {
220       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
221     }
222   }
223
224   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
225 }
226
227 void Animation::SetAnimatorsActive( bool active )
228 {
229   for ( auto&& item : mAnimators )
230   {
231     item->SetActive( active );
232   }
233 }
234
235 bool Animation::Stop(BufferIndex bufferIndex)
236 {
237   bool animationFinished(false);
238
239   if (mState == Playing || mState == Paused)
240   {
241     animationFinished = true; // The actor-thread should be notified of this
242
243     if( mEndAction != Dali::Animation::Discard )
244     {
245       Bake( bufferIndex, mEndAction );
246
247       // Animators are automatically set to inactive in Bake
248     }
249     else
250     {
251       SetAnimatorsActive( false );
252     }
253
254     // The animation has now been played to completion
255     ++mPlayedCount;
256     mCurrentLoop = 0;
257   }
258
259   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
260   mState = Stopped;
261
262   return animationFinished;
263 }
264
265 void Animation::OnDestroy(BufferIndex bufferIndex)
266 {
267   if (mState == Playing || mState == Paused)
268   {
269     if (mEndAction != Dali::Animation::Discard)
270     {
271       Bake( bufferIndex, mEndAction );
272
273       // Animators are automatically set to inactive in Bake
274     }
275     else
276     {
277       SetAnimatorsActive( false );
278     }
279   }
280
281   mState = Destroyed;
282 }
283
284 void Animation::SetLoopingMode( bool loopingMode )
285 {
286   mAutoReverseEnabled = loopingMode;
287
288   for ( auto&& item : mAnimators )
289   {
290     // Send some variables together to figure out the Animation status
291     item->SetSpeedFactor( mSpeedFactor );
292     item->SetLoopCount( mLoopCount );
293     item->SetLoopingMode( loopingMode );
294   }
295 }
296
297 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
298 {
299   animator->ConnectToSceneGraph();
300   animator->SetDisconnectAction( mDisconnectAction );
301
302   mAnimators.PushBack( animator.Release() );
303 }
304
305 void Animation::Update( BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
306 {
307   looped = false;
308   finished = false;
309
310   if (mState == Stopped || mState == Destroyed)
311   {
312     // Short circuit when animation isn't running
313     return;
314   }
315
316   // The animation must still be applied when Paused/Stopping
317   if (mState == Playing)
318   {
319     // Sign value of speed factor. It can optimize many arithmetic comparision
320     float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
321
322     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
323     if( mDelaySeconds > 0.0f )
324     {
325       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
326       if( reduceSeconds > mDelaySeconds )
327       {
328         // add overflowed time to mElapsedSecond.
329         // If speed factor > 0, add it. if speed factor < 0, subtract it.
330         float overflowSeconds = reduceSeconds - mDelaySeconds;
331         mElapsedSeconds += signSpeedFactor * overflowSeconds;
332         mDelaySeconds = 0.0f;
333       }
334       else
335       {
336         mDelaySeconds -= reduceSeconds;
337       }
338     }
339     else
340     {
341       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
342     }
343
344     const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
345     const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
346     // Final reached seconds. It can optimize many arithmetic comparision
347     float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
348
349     // Optimized Factors.
350     // elapsed >  edge   --> check if looped
351     // elapsed >= marker --> check if elapsed reached to marker in normal case
352     // edge    >= marker --> check if elapsed reached to marker in looped case
353     float elapsedFactor = signSpeedFactor * mElapsedSeconds;
354     float edgeFactor = signSpeedFactor * edgeRangeSeconds;
355     float markerFactor = signSpeedFactor * mProgressMarker;
356
357     // check it is looped
358     looped = ( elapsedFactor > edgeFactor );
359
360     if( looped )
361     {
362       WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
363
364       // Recalculate elapsedFactor here
365       elapsedFactor = signSpeedFactor * mElapsedSeconds;
366
367       if( mLoopCount != 0 )
368       {
369         // Check If this animation is finished
370         ++mCurrentLoop;
371         if( mCurrentLoop >= mLoopCount )
372         {
373           DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
374           finished = true;
375
376           // The animation has now been played to completion
377           ++mPlayedCount;
378
379           // Make elapsed second as edge of range forcely.
380           mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
381           UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
382
383           // After update animation, mElapsedSeconds must be begin of value
384           mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
385           mState = Stopped;
386         }
387       }
388
389       // when it is on looped state, 2 case to send progress signal.
390       // (require && range_value >= marker) ||         << Signal at previous loop
391       // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
392       if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
393       {
394         // The application should be notified by NotificationManager, in another thread
395         progressReached = true;
396         mProgressReachedSignalRequired = false;
397       }
398       else
399       {
400         if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
401         {
402           progressReached = true;
403         }
404         mProgressReachedSignalRequired = mProgressMarker > 0.0f;
405       }
406     }
407     else
408     {
409       // when it is not on looped state, only 1 case to send progress signal.
410       // (require && elaped >= marker)
411       if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
412       {
413         // The application should be notified by NotificationManager, in another thread
414         progressReached = true;
415         mProgressReachedSignalRequired = false;
416       }
417     }
418   }
419
420   // Already updated when finished. So skip.
421   if( !finished )
422   {
423     UpdateAnimators(bufferIndex, false, false );
424   }
425 }
426
427 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
428 {
429   const Vector2 playRange( mPlayRange * mDurationSeconds );
430   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
431
432   //Loop through all animators
433   bool applied(true);
434   for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
435   {
436     AnimatorBase *animator = *iter;
437
438     if( animator->Orphan() )
439     {
440       //Remove animators whose PropertyOwner has been destroyed
441       iter = mAnimators.Erase(iter);
442     }
443     else
444     {
445       if( animator->IsEnabled() )
446       {
447         const float intervalDelay( animator->GetIntervalDelay() );
448
449         if( elapsedSecondsClamped >= intervalDelay )
450         {
451           // Calculate a progress specific to each individual animator
452           float progress(1.0f);
453           const float animatorDuration = animator->GetDuration();
454           if (animatorDuration > 0.0f) // animators can be "immediate"
455           {
456             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
457           }
458           animator->Update(bufferIndex, progress, bake);
459         }
460         applied = true;
461       }
462       else
463       {
464         applied = false;
465       }
466
467       if ( animationFinished )
468       {
469         animator->SetActive( false );
470       }
471
472       if (applied)
473       {
474         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
475       }
476
477       ++iter;
478     }
479   }
480
481 }
482
483 } // namespace SceneGraph
484
485 } // namespace Internal
486
487 } // namespace Dali