Append Stopped animation notify list at Update Animation time
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2024 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
29 namespace // Unnamed namespace
30 {
31 // Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
32 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation>& GetAnimationMemoryPool()
33 {
34   static Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
35   return gAnimationMemoryPool;
36 }
37
38 inline void WrapInPlayRange(float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
39 {
40   if(elapsed > playRangeEndSeconds)
41   {
42     elapsed = playRangeStartSeconds + fmodf((elapsed - playRangeStartSeconds), (playRangeEndSeconds - playRangeStartSeconds));
43   }
44   else if(elapsed < playRangeStartSeconds)
45   {
46     elapsed = playRangeEndSeconds - fmodf((playRangeStartSeconds - elapsed), (playRangeEndSeconds - playRangeStartSeconds));
47   }
48 }
49
50 /// 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.
51 bool CompareAnimatorEndTimes(const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs)
52 {
53   return ((lhs->GetIntervalDelay() + lhs->GetDuration()) < (rhs->GetIntervalDelay() + rhs->GetDuration()));
54 }
55
56 } // unnamed namespace
57
58 namespace Dali
59 {
60 namespace Internal
61 {
62 namespace SceneGraph
63 {
64 Animation* Animation::New(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction)
65 {
66   return new(GetAnimationMemoryPool().AllocateRawThreadSafe()) Animation(durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction);
67 }
68
69 Animation::Animation(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction)
70 : mPlayRange(playRange),
71   mDurationSeconds(durationSeconds),
72   mDelaySeconds(0.0f),
73   mElapsedSeconds(playRange.x * mDurationSeconds),
74   mSpeedFactor(speedFactor),
75   mProgressMarker(0.0f),
76   mBlendPoint(0.0f),
77   mPlayedCount(0),
78   mLoopCount(loopCount),
79   mCurrentLoop(0),
80   mEndAction(endAction),
81   mDisconnectAction(disconnectAction),
82   mState(Stopped),
83   mProgressReachedSignalRequired(false),
84   mAutoReverseEnabled(false),
85   mAnimatorSortRequired(false),
86   mIsActive{false, false},
87   mIsFirstLoop{true}
88 {
89 }
90
91 Animation::~Animation() = default;
92
93 void Animation::operator delete(void* ptr)
94 {
95   GetAnimationMemoryPool().FreeThreadSafe(static_cast<Animation*>(ptr));
96 }
97
98 void Animation::SetDuration(float durationSeconds)
99 {
100   mDurationSeconds = durationSeconds;
101 }
102
103 void Animation::SetProgressNotification(float progress)
104 {
105   mProgressMarker = progress;
106   if(mProgressMarker > 0.0f)
107   {
108     mProgressReachedSignalRequired = true;
109   }
110 }
111
112 void Animation::SetLoopCount(int32_t loopCount)
113 {
114   mLoopCount   = loopCount;
115   mCurrentLoop = 0;
116 }
117
118 void Animation::SetEndAction(Dali::Animation::EndAction action)
119 {
120   mEndAction = action;
121 }
122
123 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
124 {
125   if(mDisconnectAction != action)
126   {
127     mDisconnectAction = action;
128
129     for(auto&& item : mAnimators)
130     {
131       item->SetDisconnectAction(action);
132     }
133   }
134 }
135
136 void Animation::SetPlayRange(const Vector2& range)
137 {
138   mPlayRange = range;
139
140   // Make sure mElapsedSeconds is within the new range
141
142   if(mState == Stopped)
143   {
144     // Ensure that the animation starts at the right place
145     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
146   }
147   else
148   {
149     // If already past the end of the range, but before end of duration, then clamp will
150     // ensure that the animation stops on the next update.
151     // If not yet at the start of the range, clamping will jump to the start
152     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x * mDurationSeconds, mPlayRange.y * mDurationSeconds);
153   }
154 }
155
156 void Animation::SetBlendPoint(float blendPoint)
157 {
158   mBlendPoint = blendPoint;
159 }
160
161 void Animation::Play()
162 {
163   if(mAnimatorSortRequired)
164   {
165     // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
166     std::stable_sort(mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes);
167     mAnimatorSortRequired = false;
168   }
169
170   // Let we don't change current loop value if the state was paused.
171   if(mState != Paused)
172   {
173     mCurrentLoop  = 0;
174     mDelaySeconds = 0.0f;
175   }
176   mState = Playing;
177
178   if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
179   {
180     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
181   }
182
183   SetAnimatorsActive(true);
184 }
185
186 void Animation::PlayFrom(float progress)
187 {
188   // If the animation is already playing this has no effect
189   // Progress is guaranteed to be in range.
190   if(mState != Playing)
191   {
192     mElapsedSeconds = progress * mDurationSeconds;
193     // Let we don't change current loop value if the state was paused.
194     if(mState != Paused)
195     {
196       mCurrentLoop  = 0;
197       mDelaySeconds = 0.0f;
198     }
199     mState = Playing;
200
201     SetAnimatorsActive(true);
202   }
203 }
204
205 void Animation::PlayAfter(float delaySeconds)
206 {
207   if(mState != Playing)
208   {
209     mDelaySeconds = delaySeconds;
210     // Let we don't change current loop value if the state was paused.
211     if(mState != Paused)
212     {
213       mCurrentLoop = 0;
214     }
215     mState = Playing;
216
217     if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
218     {
219       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
220     }
221
222     SetAnimatorsActive(true);
223   }
224 }
225
226 void Animation::Pause()
227 {
228   if(mState == Playing)
229   {
230     mState = Paused;
231   }
232 }
233
234 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
235 {
236   if(action == Dali::Animation::BAKE_FINAL)
237   {
238     if(mSpeedFactor > 0.0f)
239     {
240       mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
241     }
242     else
243     {
244       mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; // Force animation to reach it's beginning
245     }
246   }
247
248   UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
249 }
250
251 void Animation::SetAnimatorsActive(bool active)
252 {
253   for(auto&& item : mAnimators)
254   {
255     item->SetActive(active);
256   }
257 }
258
259 bool Animation::Stop(BufferIndex bufferIndex)
260 {
261   bool animationFinished(false);
262
263   if(mState == Playing || mState == Paused)
264   {
265     animationFinished = true; // The actor-thread should be notified of this
266     mIsStopped        = true;
267
268     if(mEndAction != Dali::Animation::DISCARD)
269     {
270       Bake(bufferIndex, mEndAction);
271
272       // Animators are automatically set to inactive in Bake
273     }
274     else
275     {
276       SetAnimatorsActive(false);
277     }
278
279     // The animation has now been played to completion
280     ++mPlayedCount;
281     mCurrentLoop = 0;
282   }
283
284   mDelaySeconds   = 0.0f;
285   mElapsedSeconds = mPlayRange.x * mDurationSeconds;
286   mState          = Stopped;
287   mIsFirstLoop    = true;
288
289   return animationFinished;
290 }
291
292 void Animation::ClearAnimator(BufferIndex bufferIndex)
293 {
294   // Stop animation immediatly.
295   Stop(bufferIndex);
296
297   // Remove all animator.
298   mAnimators.Clear();
299   mAnimatorSortRequired = false;
300
301   // Reset animation state values.
302   mIsStopped   = false; ///< Do not make notify.
303   mPlayedCount = 0;
304   mCurrentLoop = 0;
305 }
306
307 void Animation::OnDestroy(BufferIndex bufferIndex)
308 {
309   if(mState == Playing || mState == Paused)
310   {
311     if(mEndAction != Dali::Animation::DISCARD)
312     {
313       Bake(bufferIndex, mEndAction);
314
315       // Animators are automatically set to inactive in Bake
316     }
317     else
318     {
319       SetAnimatorsActive(false);
320     }
321   }
322
323   mIsStopped = false; ///< Do not make notify.
324   mState     = Destroyed;
325 }
326
327 void Animation::SetLoopingMode(bool loopingMode)
328 {
329   mAutoReverseEnabled = loopingMode;
330
331   for(auto&& item : mAnimators)
332   {
333     // Send some variables together to figure out the Animation status
334     item->SetSpeedFactor(mSpeedFactor);
335     item->SetLoopCount(mLoopCount);
336     item->SetLoopingMode(loopingMode);
337   }
338 }
339
340 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
341 {
342   animator->ConnectToSceneGraph();
343   animator->SetDisconnectAction(mDisconnectAction);
344
345   // Check whether we need to sort mAnimators or not.
346   // Sort will be required only if new item is smaller than last value of container.
347   if(!mAnimatorSortRequired && !mAnimators.Empty())
348   {
349     if(CompareAnimatorEndTimes(animator.Get(), *(mAnimators.End() - 1u)))
350     {
351       mAnimatorSortRequired = true;
352     }
353   }
354
355   mAnimators.PushBack(animator.Release());
356 }
357
358 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& stopped, bool& finished, bool& progressReached)
359 {
360   // Reset mIsStopped flag now.
361   stopped    = mIsStopped;
362   mIsStopped = false;
363
364   finished = false;
365
366   // Short circuit when animation isn't running
367   if(mState == Stopped || mState == Destroyed)
368   {
369     return;
370   }
371
372   // The animation must still be applied when Paused/Stopping
373   if(mState == Playing)
374   {
375     // Sign value of speed factor. It can optimize many arithmetic comparision
376     float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
377
378     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
379     if(mDelaySeconds > 0.0f)
380     {
381       float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
382       if(reduceSeconds > mDelaySeconds)
383       {
384         // add overflowed time to mElapsedSecond.
385         // If speed factor > 0, add it. if speed factor < 0, subtract it.
386         float overflowSeconds = reduceSeconds - mDelaySeconds;
387         mElapsedSeconds += signSpeedFactor * overflowSeconds;
388         mDelaySeconds = 0.0f;
389       }
390       else
391       {
392         mDelaySeconds -= reduceSeconds;
393       }
394     }
395     else
396     {
397       mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
398     }
399
400     const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
401     const float playRangeEndSeconds   = mPlayRange.y * mDurationSeconds;
402     // Final reached seconds. It can optimize many arithmetic comparision
403     float edgeRangeSeconds = (mSpeedFactor < 0.0f) ? playRangeStartSeconds : playRangeEndSeconds;
404
405     // Optimized Factors.
406     // elapsed >  edge   --> check if looped
407     // elapsed >= marker --> check if elapsed reached to marker in normal case
408     // edge    >= marker --> check if elapsed reached to marker in looped case
409     float elapsedFactor = signSpeedFactor * mElapsedSeconds;
410     float edgeFactor    = signSpeedFactor * edgeRangeSeconds;
411     float markerFactor  = signSpeedFactor * mProgressMarker;
412
413     // check it is looped
414     const bool looped = (elapsedFactor > edgeFactor);
415
416     if(looped)
417     {
418       WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
419
420       // Recalculate elapsedFactor here
421       elapsedFactor = signSpeedFactor * mElapsedSeconds;
422
423       mIsFirstLoop = false;
424       if(mLoopCount != 0)
425       {
426         // Check If this animation is finished
427         ++mCurrentLoop;
428         if(mCurrentLoop >= mLoopCount)
429         {
430           DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
431           finished = true;
432
433           // The animation has now been played to completion
434           ++mPlayedCount;
435
436           // Make elapsed second as edge of range forcely.
437           mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
438           UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::DISCARD), finished);
439
440           // After update animation, mElapsedSeconds must be begin of value
441           mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
442           mState          = Stopped;
443           mIsFirstLoop    = true;
444         }
445       }
446
447       // when it is on looped state, 2 case to send progress signal.
448       // (require && range_value >= marker) ||         << Signal at previous loop
449       // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
450       if((mProgressMarker > 0.0f) && !finished && (elapsedFactor >= markerFactor))
451       {
452         // The application should be notified by NotificationManager, in another thread
453         progressReached                = true;
454         mProgressReachedSignalRequired = false;
455       }
456       else
457       {
458         if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
459         {
460           progressReached = true;
461         }
462         mProgressReachedSignalRequired = mProgressMarker > 0.0f;
463       }
464     }
465     else
466     {
467       // when it is not on looped state, only 1 case to send progress signal.
468       // (require && elaped >= marker)
469       if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
470       {
471         // The application should be notified by NotificationManager, in another thread
472         progressReached                = true;
473         mProgressReachedSignalRequired = false;
474       }
475     }
476   }
477
478   // Already updated when finished. So skip.
479   if(!finished)
480   {
481     UpdateAnimators(bufferIndex, false, false);
482   }
483 }
484
485 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
486 {
487   mIsActive[bufferIndex] = false;
488
489   const Vector2 playRange(mPlayRange * mDurationSeconds);
490   float         elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
491
492   bool cleanup = false;
493
494   // Loop through all animators
495   for(auto& animator : mAnimators)
496   {
497     if(animator->Orphan())
498     {
499       cleanup = true;
500       continue;
501     }
502
503     bool applied(true);
504     if(animator->IsEnabled())
505     {
506       const float intervalDelay(animator->GetIntervalDelay());
507
508       if(elapsedSecondsClamped >= intervalDelay)
509       {
510         // Calculate a progress specific to each individual animator
511         float       progress(1.0f);
512         const float animatorDuration = animator->GetDuration();
513         if(animatorDuration > 0.0f) // animators can be "immediate"
514         {
515           progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
516         }
517         animator->Update(bufferIndex, progress, mIsFirstLoop ? mBlendPoint : 0.0f, bake);
518
519         if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
520         {
521           mIsActive[bufferIndex] = true;
522         }
523       }
524       else
525       {
526         animator->SetDelayed(true);
527       }
528       applied = true;
529     }
530     else
531     {
532       applied = false;
533     }
534
535     if(animationFinished)
536     {
537       animator->SetActive(false);
538     }
539
540     if(applied)
541     {
542       INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
543     }
544   }
545
546   if(cleanup)
547   {
548     // Remove animators whose PropertyOwner has been destroyed
549     mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
550
551     // Need to be re-sort if remained animators size is bigger than one.
552     // Note that if animator contains only zero or one items, It is already sorted case.
553     mAnimatorSortRequired = (mAnimators.Count() >= 2);
554   }
555 }
556
557 uint32_t Animation::GetMemoryPoolCapacity()
558 {
559   return GetAnimationMemoryPool().GetCapacity();
560 }
561
562 } // namespace SceneGraph
563
564 } // namespace Internal
565
566 } // namespace Dali