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},
91 Animation::~Animation() = default;
93 void Animation::operator delete(void* ptr)
95 GetAnimationMemoryPool().FreeThreadSafe(static_cast<Animation*>(ptr));
98 void Animation::SetDuration(float durationSeconds)
100 mDurationSeconds = durationSeconds;
103 void Animation::SetProgressNotification(float progress)
105 mProgressMarker = progress;
106 if(mProgressMarker > 0.0f)
108 mProgressReachedSignalRequired = true;
112 void Animation::SetLoopCount(int32_t loopCount)
114 mLoopCount = loopCount;
118 void Animation::SetEndAction(Dali::Animation::EndAction action)
123 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
125 if(mDisconnectAction != action)
127 mDisconnectAction = action;
129 for(auto&& item : mAnimators)
131 item->SetDisconnectAction(action);
136 void Animation::SetPlayRange(const Vector2& range)
140 // Make sure mElapsedSeconds is within the new range
142 if(mState == Stopped)
144 // Ensure that the animation starts at the right place
145 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
149 // If already past the end of the range, but before end of duration, then clamp will
150 // ensure that the animation stops on the next update.
151 // If not yet at the start of the range, clamping will jump to the start
152 mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x * mDurationSeconds, mPlayRange.y * mDurationSeconds);
156 void Animation::SetBlendPoint(float blendPoint)
158 mBlendPoint = blendPoint;
161 void Animation::Play()
163 if(mAnimatorSortRequired)
165 // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
166 std::stable_sort(mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes);
167 mAnimatorSortRequired = false;
170 // Let we don't change current loop value if the state was paused.
174 mDelaySeconds = 0.0f;
178 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
180 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
183 SetAnimatorsActive(true);
186 void Animation::PlayFrom(float progress)
188 // If the animation is already playing this has no effect
189 // Progress is guaranteed to be in range.
190 if(mState != Playing)
192 mElapsedSeconds = progress * mDurationSeconds;
193 // Let we don't change current loop value if the state was paused.
197 mDelaySeconds = 0.0f;
201 SetAnimatorsActive(true);
205 void Animation::PlayAfter(float delaySeconds)
207 if(mState != Playing)
209 mDelaySeconds = delaySeconds;
210 // Let we don't change current loop value if the state was paused.
217 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
219 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
222 SetAnimatorsActive(true);
226 void Animation::Pause()
228 if(mState == Playing)
234 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
236 if(action == Dali::Animation::BAKE_FINAL)
238 if(mSpeedFactor > 0.0f)
240 mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
244 mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; // Force animation to reach it's beginning
248 UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
251 void Animation::SetAnimatorsActive(bool active)
253 for(auto&& item : mAnimators)
255 item->SetActive(active);
259 bool Animation::Stop(BufferIndex bufferIndex)
261 bool animationFinished(false);
263 if(mState == Playing || mState == Paused)
265 animationFinished = true; // The actor-thread should be notified of this
268 if(mEndAction != Dali::Animation::DISCARD)
270 Bake(bufferIndex, mEndAction);
272 // Animators are automatically set to inactive in Bake
276 SetAnimatorsActive(false);
279 // The animation has now been played to completion
284 mDelaySeconds = 0.0f;
285 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
289 return animationFinished;
292 void Animation::ClearAnimator(BufferIndex bufferIndex)
294 // Stop animation immediatly.
297 // Remove all animator.
299 mAnimatorSortRequired = false;
301 // Reset animation state values.
302 mIsStopped = false; ///< Do not make notify.
307 void Animation::OnDestroy(BufferIndex bufferIndex)
309 if(mState == Playing || mState == Paused)
311 if(mEndAction != Dali::Animation::DISCARD)
313 Bake(bufferIndex, mEndAction);
315 // Animators are automatically set to inactive in Bake
319 SetAnimatorsActive(false);
323 mIsStopped = false; ///< Do not make notify.
327 void Animation::SetLoopingMode(bool loopingMode)
329 mAutoReverseEnabled = loopingMode;
331 for(auto&& item : mAnimators)
333 // Send some variables together to figure out the Animation status
334 item->SetSpeedFactor(mSpeedFactor);
335 item->SetLoopCount(mLoopCount);
336 item->SetLoopingMode(loopingMode);
340 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
342 animator->ConnectToSceneGraph();
343 animator->SetDisconnectAction(mDisconnectAction);
345 // Check whether we need to sort mAnimators or not.
346 // Sort will be required only if new item is smaller than last value of container.
347 if(!mAnimatorSortRequired && !mAnimators.Empty())
349 if(CompareAnimatorEndTimes(animator.Get(), *(mAnimators.End() - 1u)))
351 mAnimatorSortRequired = true;
355 mAnimators.PushBack(animator.Release());
358 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& stopped, bool& finished, bool& progressReached)
360 // Reset mIsStopped flag now.
361 stopped = mIsStopped;
366 // Short circuit when animation isn't running
367 if(mState == Stopped || mState == Destroyed)
372 // The animation must still be applied when Paused/Stopping
373 if(mState == Playing)
375 // Sign value of speed factor. It can optimize many arithmetic comparision
376 float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
378 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
379 if(mDelaySeconds > 0.0f)
381 float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
382 if(reduceSeconds > mDelaySeconds)
384 // add overflowed time to mElapsedSecond.
385 // If speed factor > 0, add it. if speed factor < 0, subtract it.
386 float overflowSeconds = reduceSeconds - mDelaySeconds;
387 mElapsedSeconds += signSpeedFactor * overflowSeconds;
388 mDelaySeconds = 0.0f;
392 mDelaySeconds -= reduceSeconds;
397 mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
400 const float playRangeStartSeconds = mPlayRange.x * mDurationSeconds;
401 const float playRangeEndSeconds = mPlayRange.y * mDurationSeconds;
402 // Final reached seconds. It can optimize many arithmetic comparision
403 float edgeRangeSeconds = (mSpeedFactor < 0.0f) ? playRangeStartSeconds : playRangeEndSeconds;
405 // Optimized Factors.
406 // elapsed > edge --> check if looped
407 // elapsed >= marker --> check if elapsed reached to marker in normal case
408 // edge >= marker --> check if elapsed reached to marker in looped case
409 float elapsedFactor = signSpeedFactor * mElapsedSeconds;
410 float edgeFactor = signSpeedFactor * edgeRangeSeconds;
411 float markerFactor = signSpeedFactor * mProgressMarker;
413 // check it is looped
414 const bool looped = (elapsedFactor > edgeFactor);
418 WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
420 // Recalculate elapsedFactor here
421 elapsedFactor = signSpeedFactor * mElapsedSeconds;
423 mIsFirstLoop = false;
426 // Check If this animation is finished
428 if(mCurrentLoop >= mLoopCount)
430 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
433 // The animation has now been played to completion
436 // Make elapsed second as edge of range forcely.
437 mElapsedSeconds = edgeRangeSeconds + signSpeedFactor * Math::MACHINE_EPSILON_10;
438 UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::DISCARD), finished);
440 // After update animation, mElapsedSeconds must be begin of value
441 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
447 // when it is on looped state, 2 case to send progress signal.
448 // (require && range_value >= marker) || << Signal at previous loop
449 // (marker > 0 && !finished && elaped >= marker) << Signal at current loop
450 if((mProgressMarker > 0.0f) && !finished && (elapsedFactor >= markerFactor))
452 // The application should be notified by NotificationManager, in another thread
453 progressReached = true;
454 mProgressReachedSignalRequired = false;
458 if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
460 progressReached = true;
462 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
467 // when it is not on looped state, only 1 case to send progress signal.
468 // (require && elaped >= marker)
469 if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
471 // The application should be notified by NotificationManager, in another thread
472 progressReached = true;
473 mProgressReachedSignalRequired = false;
478 // Already updated when finished. So skip.
481 UpdateAnimators(bufferIndex, false, false);
485 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
487 mIsActive[bufferIndex] = false;
489 const Vector2 playRange(mPlayRange * mDurationSeconds);
490 float elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
492 bool cleanup = false;
494 // Loop through all animators
495 for(auto& animator : mAnimators)
497 if(animator->Orphan())
504 if(animator->IsEnabled())
506 const float intervalDelay(animator->GetIntervalDelay());
508 if(elapsedSecondsClamped >= intervalDelay)
510 // Calculate a progress specific to each individual animator
511 float progress(1.0f);
512 const float animatorDuration = animator->GetDuration();
513 if(animatorDuration > 0.0f) // animators can be "immediate"
515 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
517 animator->Update(bufferIndex, progress, mIsFirstLoop ? mBlendPoint : 0.0f, bake);
519 if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
521 mIsActive[bufferIndex] = true;
526 animator->SetDelayed(true);
535 if(animationFinished)
537 animator->SetActive(false);
542 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
548 // Remove animators whose PropertyOwner has been destroyed
549 mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
551 // Need to be re-sort if remained animators size is bigger than one.
552 // Note that if animator contains only zero or one items, It is already sorted case.
553 mAnimatorSortRequired = (mAnimators.Count() >= 2);
557 uint32_t Animation::GetMemoryPoolCapacity()
559 return GetAnimationMemoryPool().GetCapacity();
562 } // namespace SceneGraph
564 } // namespace Internal