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