2 * Copyright (c) 2023 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>
28 namespace //Unnamed namespace
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()
33 static Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
34 return gAnimationMemoryPool;
37 inline void WrapInPlayRange(float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
39 if(elapsed > playRangeEndSeconds)
41 elapsed = playRangeStartSeconds + fmodf((elapsed - playRangeStartSeconds), (playRangeEndSeconds - playRangeStartSeconds));
43 else if(elapsed < playRangeStartSeconds)
45 elapsed = playRangeEndSeconds - fmodf((playRangeStartSeconds - elapsed), (playRangeEndSeconds - playRangeStartSeconds));
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)
52 return ((lhs->GetIntervalDelay() + lhs->GetDuration()) < (rhs->GetIntervalDelay() + rhs->GetDuration()));
55 } // unnamed namespace
63 Animation* Animation::New(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction)
65 return new(GetAnimationMemoryPool().AllocateRawThreadSafe()) Animation(durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction);
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),
72 mElapsedSeconds(playRange.x * mDurationSeconds),
73 mSpeedFactor(speedFactor),
74 mProgressMarker(0.0f),
76 mLoopCount(loopCount),
78 mEndAction(endAction),
79 mDisconnectAction(disconnectAction),
81 mProgressReachedSignalRequired(false),
82 mAutoReverseEnabled(false),
83 mAnimatorSortRequired(false),
88 Animation::~Animation() = default;
90 void Animation::operator delete(void* ptr)
92 GetAnimationMemoryPool().FreeThreadSafe(static_cast<Animation*>(ptr));
95 void Animation::SetDuration(float durationSeconds)
97 mDurationSeconds = durationSeconds;
100 void Animation::SetProgressNotification(float progress)
102 mProgressMarker = progress;
103 if(mProgressMarker > 0.0f)
105 mProgressReachedSignalRequired = true;
109 void Animation::SetLoopCount(int32_t loopCount)
111 mLoopCount = loopCount;
115 void Animation::SetEndAction(Dali::Animation::EndAction action)
120 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
122 if(mDisconnectAction != action)
124 mDisconnectAction = action;
126 for(auto&& item : mAnimators)
128 item->SetDisconnectAction(action);
133 void Animation::SetPlayRange(const Vector2& range)
137 // Make sure mElapsedSeconds is within the new range
139 if(mState == Stopped)
141 // Ensure that the animation starts at the right place
142 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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);
153 void Animation::Play()
155 if(mAnimatorSortRequired)
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;
164 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
166 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
169 SetAnimatorsActive(true);
174 void Animation::PlayFrom(float progress)
176 // If the animation is already playing this has no effect
177 // Progress is guaranteed to be in range.
178 if(mState != Playing)
180 mElapsedSeconds = progress * mDurationSeconds;
183 SetAnimatorsActive(true);
189 void Animation::PlayAfter(float delaySeconds)
191 if(mState != Playing)
193 mDelaySeconds = delaySeconds;
196 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
198 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
201 SetAnimatorsActive(true);
207 void Animation::Pause()
209 if(mState == Playing)
215 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
217 if(action == Dali::Animation::BAKE_FINAL)
219 if(mSpeedFactor > 0.0f)
221 mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
225 mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
229 UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
232 void Animation::SetAnimatorsActive(bool active)
234 for(auto&& item : mAnimators)
236 item->SetActive(active);
240 bool Animation::Stop(BufferIndex bufferIndex)
242 bool animationFinished(false);
244 if(mState == Playing || mState == Paused)
246 animationFinished = true; // The actor-thread should be notified of this
248 if(mEndAction != Dali::Animation::DISCARD)
250 Bake(bufferIndex, mEndAction);
252 // Animators are automatically set to inactive in Bake
256 SetAnimatorsActive(false);
259 // The animation has now been played to completion
264 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
267 return animationFinished;
270 void Animation::OnDestroy(BufferIndex bufferIndex)
272 if(mState == Playing || mState == Paused)
274 if(mEndAction != Dali::Animation::DISCARD)
276 Bake(bufferIndex, mEndAction);
278 // Animators are automatically set to inactive in Bake
282 SetAnimatorsActive(false);
289 void Animation::SetLoopingMode(bool loopingMode)
291 mAutoReverseEnabled = loopingMode;
293 for(auto&& item : mAnimators)
295 // Send some variables together to figure out the Animation status
296 item->SetSpeedFactor(mSpeedFactor);
297 item->SetLoopCount(mLoopCount);
298 item->SetLoopingMode(loopingMode);
302 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
304 animator->ConnectToSceneGraph();
305 animator->SetDisconnectAction(mDisconnectAction);
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())
311 if(CompareAnimatorEndTimes(animator.Get(), *(mAnimators.End() - 1u)))
313 mAnimatorSortRequired = true;
317 mAnimators.PushBack(animator.Release());
320 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached)
325 if(mState == Stopped || mState == Destroyed)
327 // Short circuit when animation isn't running
331 // The animation must still be applied when Paused/Stopping
332 if(mState == Playing)
334 // Sign value of speed factor. It can optimize many arithmetic comparision
335 float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
337 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
338 if(mDelaySeconds > 0.0f)
340 float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
341 if(reduceSeconds > mDelaySeconds)
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;
351 mDelaySeconds -= reduceSeconds;
356 mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
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;
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;
372 // check it is looped
373 looped = (elapsedFactor > edgeFactor);
377 WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
379 // Recalculate elapsedFactor here
380 elapsedFactor = signSpeedFactor * mElapsedSeconds;
384 // Check If this animation is finished
386 if(mCurrentLoop >= mLoopCount)
388 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
391 // The animation has now been played to completion
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);
398 // After update animation, mElapsedSeconds must be begin of value
399 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
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))
409 // The application should be notified by NotificationManager, in another thread
410 progressReached = true;
411 mProgressReachedSignalRequired = false;
415 if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
417 progressReached = true;
419 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
424 // when it is not on looped state, only 1 case to send progress signal.
425 // (require && elaped >= marker)
426 if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
428 // The application should be notified by NotificationManager, in another thread
429 progressReached = true;
430 mProgressReachedSignalRequired = false;
435 // Already updated when finished. So skip.
438 UpdateAnimators(bufferIndex, false, false);
442 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
444 mIsActive[bufferIndex] = false;
446 const Vector2 playRange(mPlayRange * mDurationSeconds);
447 float elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
449 bool cleanup = false;
451 //Loop through all animators
452 for(auto& animator : mAnimators)
454 if(animator->Orphan())
461 if(animator->IsEnabled())
463 const float intervalDelay(animator->GetIntervalDelay());
465 if(elapsedSecondsClamped >= intervalDelay)
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"
472 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
474 animator->Update(bufferIndex, progress, bake);
476 if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
478 mIsActive[bufferIndex] = true;
488 if(animationFinished)
490 animator->SetActive(false);
495 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
501 //Remove animators whose PropertyOwner has been destroyed
502 mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
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);
510 uint32_t Animation::GetMemoryPoolCapacity()
512 return GetAnimationMemoryPool().GetCapacity();
515 } // namespace SceneGraph
517 } // namespace Internal