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