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/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>
30 namespace // Unnamed namespace
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()
35 static Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
36 return gAnimationMemoryPool;
39 inline void WrapInPlayRange(float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
41 if(elapsed > playRangeEndSeconds)
43 elapsed = playRangeStartSeconds + fmodf((elapsed - playRangeStartSeconds), (playRangeEndSeconds - playRangeStartSeconds));
45 else if(elapsed < playRangeStartSeconds)
47 elapsed = playRangeEndSeconds - fmodf((playRangeStartSeconds - elapsed), (playRangeEndSeconds - playRangeStartSeconds));
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)
54 return ((lhs->GetIntervalDelay() + lhs->GetDuration()) < (rhs->GetIntervalDelay() + rhs->GetDuration()));
57 } // unnamed namespace
65 Animation* Animation::New(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction)
67 return new(GetAnimationMemoryPool().AllocateRawThreadSafe()) Animation(durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction);
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),
74 mElapsedSeconds(playRange.x * mDurationSeconds),
75 mSpeedFactor(speedFactor),
76 mProgressMarker(0.0f),
79 mLoopCount(loopCount),
81 mEndAction(endAction),
82 mDisconnectAction(disconnectAction),
84 mProgressReachedSignalRequired(false),
85 mAutoReverseEnabled(false),
86 mAnimatorSortRequired(false),
87 mIsActive{false, false},
93 Animation::~Animation() = default;
95 void Animation::operator delete(void* ptr)
97 GetAnimationMemoryPool().FreeThreadSafe(static_cast<Animation*>(ptr));
100 void Animation::SetDuration(float durationSeconds)
102 mDurationSeconds = durationSeconds;
105 void Animation::SetProgressNotification(float progress)
107 mProgressMarker = progress;
108 if(mProgressMarker > 0.0f)
110 mProgressReachedSignalRequired = true;
114 void Animation::SetLoopCount(int32_t loopCount)
116 mLoopCount = loopCount;
120 void Animation::SetEndAction(Dali::Animation::EndAction action)
125 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
127 if(mDisconnectAction != action)
129 mDisconnectAction = action;
131 for(auto&& item : mAnimators)
133 item->SetDisconnectAction(action);
138 void Animation::SetPlayRange(const Vector2& range)
142 // Make sure mElapsedSeconds is within the new range
144 if(mState == Stopped)
146 // Ensure that the animation starts at the right place
147 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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);
158 void Animation::SetBlendPoint(float blendPoint)
160 mBlendPoint = blendPoint;
163 void Animation::Play()
165 if(mAnimatorSortRequired)
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;
172 // Let we don't change current loop value if the state was paused.
176 mDelaySeconds = 0.0f;
180 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
182 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
185 SetAnimatorsActive(true);
188 void Animation::PlayFrom(float progress)
190 // If the animation is already playing this has no effect
191 // Progress is guaranteed to be in range.
192 if(mState != Playing)
194 mElapsedSeconds = progress * mDurationSeconds;
195 // Let we don't change current loop value if the state was paused.
199 mDelaySeconds = 0.0f;
203 SetAnimatorsActive(true);
207 void Animation::PlayAfter(float delaySeconds)
209 if(mState != Playing)
211 mDelaySeconds = delaySeconds;
212 // Let we don't change current loop value if the state was paused.
219 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
221 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
224 SetAnimatorsActive(true);
228 void Animation::Pause()
230 if(mState == Playing)
233 DALI_LOG_DEBUG_INFO("Animation[%u] with duration %f ms Pause\n", GetNotifyId(), mDurationSeconds * 1000.0f);
237 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
239 if(action == Dali::Animation::BAKE_FINAL)
241 if(mSpeedFactor > 0.0f)
243 mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
247 mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; // Force animation to reach it's beginning
251 UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
254 void Animation::SetAnimatorsActive(bool active)
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)
259 item->SetActive(active);
263 bool Animation::Stop(BufferIndex bufferIndex)
265 bool animationFinished(false);
267 if(mState == Playing || mState == Paused)
269 animationFinished = true; // The actor-thread should be notified of this
272 if(mEndAction != Dali::Animation::DISCARD)
274 Bake(bufferIndex, mEndAction);
275 DALI_LOG_DEBUG_INFO("Animation[%u] with duration %f ms Stop\n", GetNotifyId(), mDurationSeconds * 1000.0f);
277 // Animators are automatically set to inactive in Bake
281 SetAnimatorsActive(false);
284 // The animation has now been played to completion
289 mDelaySeconds = 0.0f;
290 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
294 return animationFinished;
297 void Animation::ClearAnimator(BufferIndex bufferIndex)
299 // Stop animation immediatly.
302 // Remove all animator.
304 mAnimatorSortRequired = false;
306 // Reset animation state values.
307 mIsStopped = false; ///< Do not make notify.
311 DALI_LOG_DEBUG_INFO("Animation[%u] with duration %f ms Clear\n", GetNotifyId(), mDurationSeconds * 1000.0f);
314 void Animation::OnDestroy(BufferIndex bufferIndex)
316 if(mState == Playing || mState == Paused)
318 if(mEndAction != Dali::Animation::DISCARD)
320 Bake(bufferIndex, mEndAction);
322 // Animators are automatically set to inactive in Bake
326 SetAnimatorsActive(false);
330 mIsStopped = false; ///< Do not make notify.
333 DALI_LOG_DEBUG_INFO("Animation[%u] with duration %f ms Destroy\n", GetNotifyId(), mDurationSeconds * 1000.0f);
336 void Animation::SetLoopingMode(bool loopingMode)
338 mAutoReverseEnabled = loopingMode;
340 for(auto&& item : mAnimators)
342 // Send some variables together to figure out the Animation status
343 item->SetSpeedFactor(mSpeedFactor);
344 item->SetLoopCount(mLoopCount);
345 item->SetLoopingMode(loopingMode);
349 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
351 animator->ConnectToSceneGraph();
352 animator->SetDisconnectAction(mDisconnectAction);
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())
358 if(CompareAnimatorEndTimes(animator.Get(), *(mAnimators.End() - 1u)))
360 mAnimatorSortRequired = true;
364 mAnimators.PushBack(animator.Release());
367 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& stopped, bool& finished, bool& progressReached)
369 // Reset mIsStopped flag now.
370 stopped = mIsStopped;
375 // Short circuit when animation isn't running
376 if(mState == Stopped || mState == Destroyed)
381 // The animation must still be applied when Paused/Stopping
382 if(mState == Playing)
384 // Sign value of speed factor. It can optimize many arithmetic comparision
385 float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
387 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
388 if(mDelaySeconds > 0.0f)
390 float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
391 if(reduceSeconds > mDelaySeconds)
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;
401 mDelaySeconds -= reduceSeconds;
406 mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
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;
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;
422 // check it is looped
423 const bool looped = (elapsedFactor > edgeFactor);
427 WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
429 // Recalculate elapsedFactor here
430 elapsedFactor = signSpeedFactor * mElapsedSeconds;
432 mIsFirstLoop = false;
435 // Check If this animation is finished
437 if(mCurrentLoop >= mLoopCount)
439 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
442 // The animation has now been played to completion
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);
449 // After update animation, mElapsedSeconds must be begin of value
450 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
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))
461 // The application should be notified by NotificationManager, in another thread
462 progressReached = true;
463 mProgressReachedSignalRequired = false;
467 if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
469 progressReached = true;
471 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
476 // when it is not on looped state, only 1 case to send progress signal.
477 // (require && elaped >= marker)
478 if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
480 // The application should be notified by NotificationManager, in another thread
481 progressReached = true;
482 mProgressReachedSignalRequired = false;
487 // Already updated when finished. So skip.
490 UpdateAnimators(bufferIndex, false, false);
494 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
496 mIsActive[bufferIndex] = false;
498 const Vector2 playRange(mPlayRange * mDurationSeconds);
499 float elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
501 bool cleanup = false;
503 // Loop through all animators
504 for(auto& animator : mAnimators)
506 if(animator->Orphan())
513 if(animator->IsEnabled())
515 const float intervalDelay(animator->GetIntervalDelay());
517 if(elapsedSecondsClamped >= intervalDelay)
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"
524 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
526 animator->Update(bufferIndex, progress, mIsFirstLoop ? mBlendPoint : 0.0f, bake);
528 if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
530 mIsActive[bufferIndex] = true;
535 animator->SetDelayed(true);
544 if(animationFinished)
546 animator->SetActive(false);
551 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
557 // Remove animators whose PropertyOwner has been destroyed
558 mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
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);
566 uint32_t Animation::GetMemoryPoolCapacity()
568 return GetAnimationMemoryPool().GetCapacity();
571 } // namespace SceneGraph
573 } // namespace Internal