2 * Copyright (c) 2022 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> gAnimationMemoryPool;
33 inline void WrapInPlayRange(float& elapsed, const float& playRangeStartSeconds, const float& playRangeEndSeconds)
35 if(elapsed > playRangeEndSeconds)
37 elapsed = playRangeStartSeconds + fmodf((elapsed - playRangeStartSeconds), (playRangeEndSeconds - playRangeStartSeconds));
39 else if(elapsed < playRangeStartSeconds)
41 elapsed = playRangeEndSeconds - fmodf((playRangeStartSeconds - elapsed), (playRangeEndSeconds - playRangeStartSeconds));
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)
48 return ((lhs->GetIntervalDelay() + lhs->GetDuration()) < (rhs->GetIntervalDelay() + rhs->GetDuration()));
51 } // unnamed namespace
59 Animation* Animation::New(float durationSeconds, float speedFactor, const Vector2& playRange, int32_t loopCount, EndAction endAction, EndAction disconnectAction)
61 return new(gAnimationMemoryPool.AllocateRawThreadSafe()) Animation(durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction);
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),
68 mElapsedSeconds(playRange.x * mDurationSeconds),
69 mSpeedFactor(speedFactor),
70 mProgressMarker(0.0f),
72 mLoopCount(loopCount),
74 mEndAction(endAction),
75 mDisconnectAction(disconnectAction),
77 mProgressReachedSignalRequired(false),
78 mAutoReverseEnabled(false),
83 Animation::~Animation() = default;
85 void Animation::operator delete(void* ptr)
87 gAnimationMemoryPool.FreeThreadSafe(static_cast<Animation*>(ptr));
90 void Animation::SetDuration(float durationSeconds)
92 mDurationSeconds = durationSeconds;
95 void Animation::SetProgressNotification(float progress)
97 mProgressMarker = progress;
98 if(mProgressMarker > 0.0f)
100 mProgressReachedSignalRequired = true;
104 void Animation::SetLoopCount(int32_t loopCount)
106 mLoopCount = loopCount;
110 void Animation::SetEndAction(Dali::Animation::EndAction action)
115 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
117 if(mDisconnectAction != action)
119 mDisconnectAction = action;
121 for(auto&& item : mAnimators)
123 item->SetDisconnectAction(action);
128 void Animation::SetPlayRange(const Vector2& range)
132 // Make sure mElapsedSeconds is within the new range
134 if(mState == Stopped)
136 // Ensure that the animation starts at the right place
137 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
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);
148 void Animation::Play()
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);
155 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
157 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
160 SetAnimatorsActive(true);
165 void Animation::PlayFrom(float progress)
167 // If the animation is already playing this has no effect
168 // Progress is guaranteed to be in range.
169 if(mState != Playing)
171 mElapsedSeconds = progress * mDurationSeconds;
174 SetAnimatorsActive(true);
180 void Animation::PlayAfter(float delaySeconds)
182 if(mState != Playing)
184 mDelaySeconds = delaySeconds;
187 if(mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x * mDurationSeconds)
189 mElapsedSeconds = mPlayRange.y * mDurationSeconds;
192 SetAnimatorsActive(true);
198 void Animation::Pause()
200 if(mState == Playing)
206 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
208 if(action == Dali::Animation::BAKE_FINAL)
210 if(mSpeedFactor > 0.0f)
212 mElapsedSeconds = mPlayRange.y * mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
216 mElapsedSeconds = mPlayRange.x * mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
220 UpdateAnimators(bufferIndex, true /*bake the final result*/, true /*animation finished*/);
223 void Animation::SetAnimatorsActive(bool active)
225 for(auto&& item : mAnimators)
227 item->SetActive(active);
231 bool Animation::Stop(BufferIndex bufferIndex)
233 bool animationFinished(false);
235 if(mState == Playing || mState == Paused)
237 animationFinished = true; // The actor-thread should be notified of this
239 if(mEndAction != Dali::Animation::DISCARD)
241 Bake(bufferIndex, mEndAction);
243 // Animators are automatically set to inactive in Bake
247 SetAnimatorsActive(false);
250 // The animation has now been played to completion
255 mElapsedSeconds = mPlayRange.x * mDurationSeconds;
258 return animationFinished;
261 void Animation::OnDestroy(BufferIndex bufferIndex)
263 if(mState == Playing || mState == Paused)
265 if(mEndAction != Dali::Animation::DISCARD)
267 Bake(bufferIndex, mEndAction);
269 // Animators are automatically set to inactive in Bake
273 SetAnimatorsActive(false);
280 void Animation::SetLoopingMode(bool loopingMode)
282 mAutoReverseEnabled = loopingMode;
284 for(auto&& item : mAnimators)
286 // Send some variables together to figure out the Animation status
287 item->SetSpeedFactor(mSpeedFactor);
288 item->SetLoopCount(mLoopCount);
289 item->SetLoopingMode(loopingMode);
293 void Animation::AddAnimator(OwnerPointer<AnimatorBase>& animator)
295 animator->ConnectToSceneGraph();
296 animator->SetDisconnectAction(mDisconnectAction);
298 mAnimators.PushBack(animator.Release());
301 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached)
306 if(mState == Stopped || mState == Destroyed)
308 // Short circuit when animation isn't running
312 // The animation must still be applied when Paused/Stopping
313 if(mState == Playing)
315 // Sign value of speed factor. It can optimize many arithmetic comparision
316 float signSpeedFactor = (mSpeedFactor < 0.0f) ? -1.f : 1.f;
318 // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
319 if(mDelaySeconds > 0.0f)
321 float reduceSeconds = fabsf(elapsedSeconds * mSpeedFactor);
322 if(reduceSeconds > mDelaySeconds)
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;
332 mDelaySeconds -= reduceSeconds;
337 mElapsedSeconds += (elapsedSeconds * mSpeedFactor);
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;
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;
353 // check it is looped
354 looped = (elapsedFactor > edgeFactor);
358 WrapInPlayRange(mElapsedSeconds, playRangeStartSeconds, playRangeEndSeconds);
360 // Recalculate elapsedFactor here
361 elapsedFactor = signSpeedFactor * mElapsedSeconds;
365 // Check If this animation is finished
367 if(mCurrentLoop >= mLoopCount)
369 DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
372 // The animation has now been played to completion
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);
379 // After update animation, mElapsedSeconds must be begin of value
380 mElapsedSeconds = playRangeStartSeconds + playRangeEndSeconds - edgeRangeSeconds;
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))
390 // The application should be notified by NotificationManager, in another thread
391 progressReached = true;
392 mProgressReachedSignalRequired = false;
396 if(mProgressReachedSignalRequired && (edgeFactor >= markerFactor))
398 progressReached = true;
400 mProgressReachedSignalRequired = mProgressMarker > 0.0f;
405 // when it is not on looped state, only 1 case to send progress signal.
406 // (require && elaped >= marker)
407 if(mProgressReachedSignalRequired && (elapsedFactor >= markerFactor))
409 // The application should be notified by NotificationManager, in another thread
410 progressReached = true;
411 mProgressReachedSignalRequired = false;
416 // Already updated when finished. So skip.
419 UpdateAnimators(bufferIndex, false, false);
423 void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake, bool animationFinished)
425 mIsActive[bufferIndex] = false;
427 const Vector2 playRange(mPlayRange * mDurationSeconds);
428 float elapsedSecondsClamped = Clamp(mElapsedSeconds, playRange.x, playRange.y);
430 bool cleanup = false;
432 //Loop through all animators
433 for(auto& animator : mAnimators)
435 if(animator->Orphan())
442 if(animator->IsEnabled())
444 const float intervalDelay(animator->GetIntervalDelay());
446 if(elapsedSecondsClamped >= intervalDelay)
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"
453 progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f, 1.0f);
455 animator->Update(bufferIndex, progress, bake);
457 if(animatorDuration > 0.0f && (elapsedSecondsClamped - intervalDelay) <= animatorDuration)
459 mIsActive[bufferIndex] = true;
469 if(animationFinished)
471 animator->SetActive(false);
476 INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
482 //Remove animators whose PropertyOwner has been destroyed
483 mAnimators.EraseIf([](auto& animator) { return animator->Orphan(); });
487 uint32_t Animation::GetMemoryPoolCapacity()
489 return gAnimationMemoryPool.GetCapacity();
492 } // namespace SceneGraph
494 } // namespace Internal