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