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