2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/update/animation/scene-graph-animation.h>
22 #include <cmath> // fmod
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>
29 namespace // Unnamed namespace
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()
34 static Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
35 return gAnimationMemoryPool;
38 inline void WrapInPlayRange(float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
40 if(elapsed > playRangeEndSeconds)
42 elapsed = playRangeStartSeconds + fmodf((elapsed - playRangeStartSeconds), (playRangeEndSeconds - playRangeStartSeconds));
44 else if(elapsed < playRangeStartSeconds)
46 elapsed = playRangeEndSeconds - fmodf((playRangeStartSeconds - elapsed), (playRangeEndSeconds - playRangeStartSeconds));
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)
53 return ((lhs->GetIntervalDelay() + lhs->GetDuration()) < (rhs->GetIntervalDelay() + rhs->GetDuration()));
56 } // unnamed namespace
64 Animation* Animation::New(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction)
66 return new(GetAnimationMemoryPool().AllocateRawThreadSafe()) Animation(durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction);
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),
73 mElapsedSeconds(playRange.x * mDurationSeconds),
74 mSpeedFactor(speedFactor),
75 mProgressMarker(0.0f),
78 mLoopCount(loopCount),
80 mEndAction(endAction),
81 mDisconnectAction(disconnectAction),
83 mProgressReachedSignalRequired(false),
84 mAutoReverseEnabled(false),
85 mAnimatorSortRequired(false),
86 mIsActive{false, false},
92 Animation::~Animation() = default;
94 void Animation::operator delete(void* ptr)
96 GetAnimationMemoryPool().FreeThreadSafe(static_cast<Animation*>(ptr));
99 void Animation::SetDuration(float durationSeconds)
101 mDurationSeconds = durationSeconds;
104 void Animation::SetProgressNotification(float progress)
106 mProgressMarker = progress;
107 if(mProgressMarker > 0.0f)
109 mProgressReachedSignalRequired = true;
113 void Animation::SetLoopCount(int32_t loopCount)
115 mLoopCount = loopCount;
119 void Animation::SetEndAction(Dali::Animation::EndAction action)
124 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
126 if(mDisconnectAction != action)
128 mDisconnectAction = action;
130 for(auto&& item : mAnimators)
132 item->SetDisconnectAction(action);
137 void Animation::SetPlayRange(const Vector2& range)
141 // Make sure mElapsedSeconds is within the new range
143 if(mState == Stopped)
145 // Ensure that the animation starts at the right place
146 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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);
157 void Animation::SetBlendPoint(float blendPoint)
159 mBlendPoint = blendPoint;
162 void Animation::Play()
164 if(mAnimatorSortRequired)
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;
171 // Let we don't change current loop value if the state was paused.
175 mDelaySeconds = 0.0f;
179 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
181 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
184 SetAnimatorsActive(true);
187 void Animation::PlayFrom(float progress)
189 // If the animation is already playing this has no effect
190 // Progress is guaranteed to be in range.
191 if(mState != Playing)
193 mElapsedSeconds = progress * mDurationSeconds;
194 // Let we don't change current loop value if the state was paused.
198 mDelaySeconds = 0.0f;
202 SetAnimatorsActive(true);
206 void Animation::PlayAfter(float delaySeconds)
208 if(mState != Playing)
210 mDelaySeconds = delaySeconds;
211 // Let we don't change current loop value if the state was paused.
218 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
220 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
223 SetAnimatorsActive(true);
227 void Animation::Pause()
229 if(mState == Playing)
235 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
237 if(action == Dali::Animation::BAKE_FINAL)
239 if(mSpeedFactor > 0.0f)
241 mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
245 mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; // Force animation to reach it's beginning
249 UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
252 void Animation::SetAnimatorsActive(bool active)
254 for(auto&& item : mAnimators)
256 item->SetActive(active);
260 bool Animation::Stop(BufferIndex bufferIndex)
262 bool animationFinished(false);
264 if(mState == Playing || mState == Paused)
266 animationFinished = true; // The actor-thread should be notified of this
269 if(mEndAction != Dali::Animation::DISCARD)
271 Bake(bufferIndex, mEndAction);
273 // Animators are automatically set to inactive in Bake
277 SetAnimatorsActive(false);
280 // The animation has now been played to completion
285 mDelaySeconds = 0.0f;
286 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
290 return animationFinished;
293 void Animation::ClearAnimator(BufferIndex bufferIndex)
295 // Stop animation immediatly.
298 // Remove all animator.
300 mAnimatorSortRequired = false;
302 // Reset animation state values.
303 mIsStopped = false; ///< Do not make notify.
308 void Animation::OnDestroy(BufferIndex bufferIndex)
310 if(mState == Playing || mState == Paused)
312 if(mEndAction != Dali::Animation::DISCARD)
314 Bake(bufferIndex, mEndAction);
316 // Animators are automatically set to inactive in Bake
320 SetAnimatorsActive(false);
324 mIsStopped = false; ///< Do not make notify.
328 void Animation::SetLoopingMode(bool loopingMode)
330 mAutoReverseEnabled = loopingMode;
332 for(auto&& item : mAnimators)
334 // Send some variables together to figure out the Animation status
335 item->SetSpeedFactor(mSpeedFactor);
336 item->SetLoopCount(mLoopCount);
337 item->SetLoopingMode(loopingMode);
341 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
343 animator->ConnectToSceneGraph();
344 animator->SetDisconnectAction(mDisconnectAction);
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())
350 if(CompareAnimatorEndTimes(animator.Get(), *(mAnimators.End() - 1u)))
352 mAnimatorSortRequired = true;
356 mAnimators.PushBack(animator.Release());
359 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& stopped, bool& finished, bool& progressReached)
361 // Reset mIsStopped flag now.
362 stopped = mIsStopped;
367 // Short circuit when animation isn't running
368 if(mState == Stopped || mState == Destroyed)
373 // The animation must still be applied when Paused/Stopping
374 if(mState == Playing)
376 // Sign value of speed factor. It can optimize many arithmetic comparision
377 float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
379 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
380 if(mDelaySeconds > 0.0f)
382 float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
383 if(reduceSeconds > mDelaySeconds)
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;
393 mDelaySeconds -= reduceSeconds;
398 mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
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;
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;
414 // check it is looped
415 const bool looped = (elapsedFactor > edgeFactor);
419 WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
421 // Recalculate elapsedFactor here
422 elapsedFactor = signSpeedFactor * mElapsedSeconds;
424 mIsFirstLoop = false;
427 // Check If this animation is finished
429 if(mCurrentLoop >= mLoopCount)
431 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
434 // The animation has now been played to completion
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);
441 // After update animation, mElapsedSeconds must be begin of value
442 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
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))
453 // The application should be notified by NotificationManager, in another thread
454 progressReached = true;
455 mProgressReachedSignalRequired = false;
459 if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
461 progressReached = true;
463 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
468 // when it is not on looped state, only 1 case to send progress signal.
469 // (require && elaped >= marker)
470 if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
472 // The application should be notified by NotificationManager, in another thread
473 progressReached = true;
474 mProgressReachedSignalRequired = false;
479 // Already updated when finished. So skip.
482 UpdateAnimators(bufferIndex, false, false);
486 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
488 mIsActive[bufferIndex] = false;
490 const Vector2 playRange(mPlayRange * mDurationSeconds);
491 float elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
493 bool cleanup = false;
495 // Loop through all animators
496 for(auto& animator : mAnimators)
498 if(animator->Orphan())
505 if(animator->IsEnabled())
507 const float intervalDelay(animator->GetIntervalDelay());
509 if(elapsedSecondsClamped >= intervalDelay)
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"
516 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
518 animator->Update(bufferIndex, progress, mIsFirstLoop ? mBlendPoint : 0.0f, bake);
520 if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
522 mIsActive[bufferIndex] = true;
527 animator->SetDelayed(true);
536 if(animationFinished)
538 animator->SetActive(false);
543 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
549 // Remove animators whose PropertyOwner has been destroyed
550 mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
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);
558 uint32_t Animation::GetMemoryPoolCapacity()
560 return GetAnimationMemoryPool().GetCapacity();
563 } // namespace SceneGraph
565 } // namespace Internal