[Tizen] Revert "Skip rendering if no animation is currently active"
[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() = default;
86
87 void Animation::operator delete( void* ptr )
88 {
89   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
90 }
91
92 void Animation::SetDuration(float durationSeconds)
93 {
94   mDurationSeconds = durationSeconds;
95 }
96
97 void Animation::SetProgressNotification( float progress )
98 {
99   mProgressMarker = progress;
100   if ( mProgressMarker > 0.0f )
101   {
102     mProgressReachedSignalRequired = true;
103   }
104 }
105
106 void Animation::SetLoopCount(int32_t loopCount)
107 {
108   mLoopCount = loopCount;
109   mCurrentLoop = 0;
110 }
111
112 void Animation::SetEndAction(Dali::Animation::EndAction action)
113 {
114   mEndAction = action;
115 }
116
117 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
118 {
119   if ( mDisconnectAction != action )
120   {
121     mDisconnectAction = action;
122
123     for ( auto&& item : mAnimators )
124     {
125       item->SetDisconnectAction( action );
126     }
127   }
128 }
129
130 void Animation::SetPlayRange( const Vector2& range )
131 {
132   mPlayRange = range;
133
134   // Make sure mElapsedSeconds is within the new range
135
136   if( mState == Stopped )
137   {
138     // Ensure that the animation starts at the right place
139     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
140   }
141   else
142   {
143     // If already past the end of the range, but before end of duration, then clamp will
144     // ensure that the animation stops on the next update.
145     // If not yet at the start of the range, clamping will jump to the start
146     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
147   }
148 }
149
150 void Animation::Play()
151 {
152   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
153   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
154
155   mState = Playing;
156
157   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
158   {
159     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
160   }
161
162   SetAnimatorsActive( true );
163
164   mCurrentLoop = 0;
165 }
166
167 void Animation::PlayFrom( float progress )
168 {
169   // If the animation is already playing this has no effect
170   // Progress is guaranteed to be in range.
171   if( mState != Playing )
172   {
173     mElapsedSeconds = progress * mDurationSeconds;
174     mState = Playing;
175
176     SetAnimatorsActive( true );
177
178     mCurrentLoop = 0;
179   }
180 }
181
182 void Animation::PlayAfter( float delaySeconds )
183 {
184   if( mState != Playing )
185   {
186     mDelaySeconds = delaySeconds;
187     mState = Playing;
188
189     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
190     {
191       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
192     }
193
194     SetAnimatorsActive( true );
195
196     mCurrentLoop = 0;
197   }
198 }
199
200 void Animation::Pause()
201 {
202   if (mState == Playing)
203   {
204     mState = Paused;
205   }
206 }
207
208 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
209 {
210   if( action == Dali::Animation::BAKE_FINAL )
211   {
212     if( mSpeedFactor > 0.0f )
213     {
214       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
215     }
216     else
217     {
218       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
219     }
220   }
221
222   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
223 }
224
225 void Animation::SetAnimatorsActive( bool active )
226 {
227   for ( auto&& item : mAnimators )
228   {
229     item->SetActive( active );
230   }
231 }
232
233 bool Animation::Stop(BufferIndex bufferIndex)
234 {
235   bool animationFinished(false);
236
237   if (mState == Playing || mState == Paused)
238   {
239     animationFinished = true; // The actor-thread should be notified of this
240
241     if( mEndAction != Dali::Animation::DISCARD )
242     {
243       Bake( bufferIndex, mEndAction );
244
245       // Animators are automatically set to inactive in Bake
246     }
247     else
248     {
249       SetAnimatorsActive( false );
250     }
251
252     // The animation has now been played to completion
253     ++mPlayedCount;
254     mCurrentLoop = 0;
255   }
256
257   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
258   mState = Stopped;
259
260   return animationFinished;
261 }
262
263 void Animation::OnDestroy(BufferIndex bufferIndex)
264 {
265   if (mState == Playing || mState == Paused)
266   {
267     if (mEndAction != Dali::Animation::DISCARD)
268     {
269       Bake( bufferIndex, mEndAction );
270
271       // Animators are automatically set to inactive in Bake
272     }
273     else
274     {
275       SetAnimatorsActive( false );
276     }
277   }
278
279   mState = Destroyed;
280 }
281
282 void Animation::SetLoopingMode( bool loopingMode )
283 {
284   mAutoReverseEnabled = loopingMode;
285
286   for ( auto&& item : mAnimators )
287   {
288     // Send some variables together to figure out the Animation status
289     item->SetSpeedFactor( mSpeedFactor );
290     item->SetLoopCount( mLoopCount );
291     item->SetLoopingMode( loopingMode );
292   }
293 }
294
295 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
296 {
297   animator->ConnectToSceneGraph();
298   animator->SetDisconnectAction( mDisconnectAction );
299
300   mAnimators.PushBack( animator.Release() );
301 }
302
303 void Animation::Update( BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
304 {
305   looped = false;
306   finished = false;
307
308   if (mState == Stopped || mState == Destroyed)
309   {
310     // Short circuit when animation isn't running
311     return;
312   }
313
314   // The animation must still be applied when Paused/Stopping
315   if (mState == Playing)
316   {
317     // Sign value of speed factor. It can optimize many arithmetic comparision
318     float signSpeedFactor = ( mSpeedFactor < 0.0f ) ? -1.f : 1.f;
319
320     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
321     if( mDelaySeconds > 0.0f )
322     {
323       float reduceSeconds = fabsf( elapsedSeconds * mSpeedFactor );
324       if( reduceSeconds > mDelaySeconds )
325       {
326         // add overflowed time to mElapsedSecond.
327         // If speed factor > 0, add it. if speed factor < 0, subtract it.
328         float overflowSeconds = reduceSeconds - mDelaySeconds;
329         mElapsedSeconds += signSpeedFactor * overflowSeconds;
330         mDelaySeconds = 0.0f;
331       }
332       else
333       {
334         mDelaySeconds -= reduceSeconds;
335       }
336     }
337     else
338     {
339       mElapsedSeconds += ( elapsedSeconds * mSpeedFactor );
340     }
341
342     const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
343     const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
344     // Final reached seconds. It can optimize many arithmetic comparision
345     float edgeRangeSeconds = ( mSpeedFactor < 0.0f ) ? playRangeStartSeconds : playRangeEndSeconds;
346
347     // Optimized Factors.
348     // elapsed >  edge   --> check if looped
349     // elapsed >= marker --> check if elapsed reached to marker in normal case
350     // edge    >= marker --> check if elapsed reached to marker in looped case
351     float elapsedFactor = signSpeedFactor * mElapsedSeconds;
352     float edgeFactor = signSpeedFactor * edgeRangeSeconds;
353     float markerFactor = signSpeedFactor * mProgressMarker;
354
355     // check it is looped
356     looped = ( elapsedFactor > edgeFactor );
357
358     if( looped )
359     {
360       WrapInPlayRange( mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds );
361
362       // Recalculate elapsedFactor here
363       elapsedFactor = signSpeedFactor * mElapsedSeconds;
364
365       if( mLoopCount != 0 )
366       {
367         // Check If this animation is finished
368         ++mCurrentLoop;
369         if( mCurrentLoop >= mLoopCount )
370         {
371           DALI_ASSERT_DEBUG( mCurrentLoop == mLoopCount );
372           finished = true;
373
374           // The animation has now been played to completion
375           ++mPlayedCount;
376
377           // Make elapsed second as edge of range forcely.
378           mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
379           UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::DISCARD), finished );
380
381           // After update animation, mElapsedSeconds must be begin of value
382           mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
383           mState = Stopped;
384         }
385       }
386
387       // when it is on looped state, 2 case to send progress signal.
388       // (require && range_value >= marker) ||         << Signal at previous loop
389       // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
390       if( ( mProgressMarker > 0.0f ) && !finished && ( elapsedFactor >= markerFactor ) )
391       {
392         // The application should be notified by NotificationManager, in another thread
393         progressReached = true;
394         mProgressReachedSignalRequired = false;
395       }
396       else
397       {
398         if( mProgressReachedSignalRequired && ( edgeFactor >= markerFactor ) )
399         {
400           progressReached = true;
401         }
402         mProgressReachedSignalRequired = mProgressMarker > 0.0f;
403       }
404     }
405     else
406     {
407       // when it is not on looped state, only 1 case to send progress signal.
408       // (require && elaped >= marker)
409       if( mProgressReachedSignalRequired && ( elapsedFactor >= markerFactor ) )
410       {
411         // The application should be notified by NotificationManager, in another thread
412         progressReached = true;
413         mProgressReachedSignalRequired = false;
414       }
415     }
416   }
417
418   // Already updated when finished. So skip.
419   if( !finished )
420   {
421     UpdateAnimators(bufferIndex, false, false );
422   }
423 }
424
425 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
426 {
427   const Vector2 playRange( mPlayRange * mDurationSeconds );
428   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
429
430   //Loop through all animators
431   bool applied(true);
432   for ( auto&& iter = mAnimators.Begin(); iter != mAnimators.End(); )
433   {
434     AnimatorBase *animator = *iter;
435
436     if( animator->Orphan() )
437     {
438       //Remove animators whose PropertyOwner has been destroyed
439       iter = mAnimators.Erase(iter);
440     }
441     else
442     {
443       if( animator->IsEnabled() )
444       {
445         const float intervalDelay( animator->GetIntervalDelay() );
446
447         if( elapsedSecondsClamped >= intervalDelay )
448         {
449           // Calculate a progress specific to each individual animator
450           float progress(1.0f);
451           const float animatorDuration = animator->GetDuration();
452           if (animatorDuration > 0.0f) // animators can be "immediate"
453           {
454             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
455           }
456           animator->Update(bufferIndex, progress, bake);
457         }
458         applied = true;
459       }
460       else
461       {
462         applied = false;
463       }
464
465       if ( animationFinished )
466       {
467         animator->SetActive( false );
468       }
469
470       if (applied)
471       {
472         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
473       }
474
475       ++iter;
476     }
477   }
478
479 }
480
481 } // namespace SceneGraph
482
483 } // namespace Internal
484
485 } // namespace Dali