It is possible that AnimationFinished trigger comes "after" event thread Play action comes.
If then, user's Play action be skipped.
To avoid this case, let we give the id when play state changed to Play / Pause / Stop.
And send finished signal only if the id is matched what visual know.
Change-Id: I03409e65d2f6b947940ea5591959b106ff8de2bd
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
mPlacementActor(),
mPlayState(DevelImageVisual::PlayState::STOPPED),
mEventCallback(nullptr),
+ mLastSentPlayStateId(0u),
mLoadFailed(false),
mRendererAdded(false),
mCoreShutdown(false),
{
if(IsOnScene() && mVisualSize != Vector2::ZERO)
{
- if(mAnimationData.playState != DevelImageVisual::PlayState::PLAYING)
- {
- mAnimationData.playState = DevelImageVisual::PlayState::PLAYING;
- mAnimationData.resendFlag |= VectorAnimationTask::RESEND_PLAY_STATE;
- }
+ // Always resend Playing state. If task is already playing, it will be ignored at Rasterize time.
+ mAnimationData.playState = DevelImageVisual::PlayState::PLAYING;
+ mAnimationData.resendFlag |= VectorAnimationTask::RESEND_PLAY_STATE;
}
mPlayState = DevelImageVisual::PlayState::PLAYING;
break;
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "status = %d [%p]\n", status, this);
}
-void AnimatedVectorImageVisual::OnAnimationFinished()
+void AnimatedVectorImageVisual::OnAnimationFinished(uint32_t playStateId)
{
+ // Only send event when animation is finished by the last Play/Pause/Stop request.
+ if(mLastSentPlayStateId != playStateId)
+ {
+ return;
+ }
+
AnimatedVectorImageVisualPtr self = this; // Keep reference until this API finished
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "AnimatedVectorImageVisual::OnAnimationFinished: action state = %d [%p]\n", mPlayState, this);
{
if(mAnimationData.resendFlag)
{
+ if(mAnimationData.resendFlag & VectorAnimationTask::RESEND_PLAY_STATE)
+ {
+ // Keep last sent playId. It will be used when we try to emit AnimationFinished signal.
+ // The OnAnimationFinished signal what before Play/Pause/Stop action send could be come after action sent.
+ // To ensure the OnAnimationFinished signal comes belong to what we sent, we need to keep last sent playId.
+ mAnimationData.playStateId = ++mLastSentPlayStateId;
+ }
mVectorAnimationTask->SetAnimationData(mAnimationData);
if(mImpl->mRenderer)
/**
* @brief Event callback from rasterize thread. This is called after the animation is finished.
+ *
+ * @param[in] playStateId The play state id
*/
- void OnAnimationFinished();
+ void OnAnimationFinished(uint32_t playStateId);
/**
* @brief Send animation data to the rasterize thread.
DevelImageVisual::PlayState::Type mPlayState;
CallbackBase* mEventCallback; // Not owned
+ uint32_t mLastSentPlayStateId;
+
bool mLoadFailed : 1;
bool mRendererAdded : 1;
bool mCoreShutdown : 1;
mWidth(0),
mHeight(0),
mAnimationDataIndex(0),
+ mAppliedPlayStateId(0u),
mLoopCount(LOOP_FOREVER),
mCurrentLoop(0),
mForward(true),
Mutex::ScopedLock lock(mMutex);
if(!synchronousLoading && mLoadCompletedCallback)
{
- mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get());
+ mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get(), 0u);
}
}
#ifdef TRACE_ENABLED
Mutex::ScopedLock lock(mMutex);
if(!synchronousLoading && mLoadCompletedCallback)
{
- mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get());
+ mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get(), 0u);
}
}
{
Load(true);
- OnLoadCompleted();
+ OnLoadCompleted(0u);
}
}
Mutex::ScopedLock lock(mMutex);
if(mNeedAnimationFinishedTrigger && mAnimationFinishedCallback)
{
- mVectorAnimationThread.AddEventTriggerCallback(mAnimationFinishedCallback.get());
+ mVectorAnimationThread.AddEventTriggerCallback(mAnimationFinishedCallback.get(), mAppliedPlayStateId);
}
}
if(animationData.resendFlag & VectorAnimationTask::RESEND_PLAY_STATE)
{
+ mAppliedPlayStateId = animationData.playStateId;
if(animationData.playState == DevelImageVisual::PlayState::PLAYING)
{
PlayAnimation();
mResourceReadySignal.Emit(ResourceStatus::READY);
}
-void VectorAnimationTask::OnLoadCompleted()
+void VectorAnimationTask::OnLoadCompleted(uint32_t /* not used */)
{
if(!mLoadFailed)
{
currentFrame(0),
width(0),
height(0),
- loopCount(-1)
+ loopCount(-1),
+ playStateId(0)
{
}
width = rhs.width;
height = rhs.height;
loopCount = rhs.loopCount;
+ playStateId = rhs.playStateId;
dynamicProperties.insert(dynamicProperties.end(), rhs.dynamicProperties.begin(), rhs.dynamicProperties.end());
return *this;
}
uint32_t width;
uint32_t height;
int32_t loopCount;
+ uint32_t playStateId;
};
/**
/**
* @brief Event callback from rasterize thread. This is called when the file loading is completed.
*/
- void OnLoadCompleted();
+ void OnLoadCompleted(uint32_t argument);
// Undefined
VectorAnimationTask(const VectorAnimationTask& task) = delete;
uint32_t mWidth;
uint32_t mHeight;
uint32_t mAnimationDataIndex;
+ uint32_t mAppliedPlayStateId;
int32_t mLoopCount;
int32_t mCurrentLoop;
bool mForward : 1;
}
}
-void VectorAnimationThread::AddEventTriggerCallback(CallbackBase* callback)
+void VectorAnimationThread::AddEventTriggerCallback(CallbackBase* callback, uint32_t argument)
{
Mutex::ScopedLock lock(mEventTriggerMutex);
if(!mDestroyThread)
{
- mTriggerEventCallbacks.push_back(callback);
+ mTriggerEventCallbacks.emplace_back(callback, argument);
if(!mEventTriggered)
{
Mutex::ScopedLock lock(mEventTriggerMutex);
if(!mDestroyThread)
{
- auto iter = std::remove(mTriggerEventCallbacks.begin(), mTriggerEventCallbacks.end(), callback);
+ auto iter = std::remove_if(mTriggerEventCallbacks.begin(), mTriggerEventCallbacks.end(), [&callback](std::pair<CallbackBase*, uint32_t>& item) { return item.first == callback; });
mTriggerEventCallbacks.erase(iter, mTriggerEventCallbacks.end());
}
}
void VectorAnimationThread::OnEventCallbackTriggered()
{
- while(CallbackBase* callback = GetNextEventCallback())
+ while(true)
{
- CallbackBase::Execute(*callback);
+ auto callbackPair = GetNextEventCallback();
+ if(callbackPair.first == nullptr)
+ {
+ break;
+ }
+ CallbackBase::Execute(*callbackPair.first, callbackPair.second);
}
}
-CallbackBase* VectorAnimationThread::GetNextEventCallback()
+std::pair<CallbackBase*, uint32_t> VectorAnimationThread::GetNextEventCallback()
{
Mutex::ScopedLock lock(mEventTriggerMutex);
if(!mDestroyThread)
{
if(!mTriggerEventCallbacks.empty())
{
- auto iter = mTriggerEventCallbacks.begin();
- CallbackBase* callback = *iter;
+ auto iter = mTriggerEventCallbacks.begin();
+ auto callbackIdPair = *iter;
mTriggerEventCallbacks.erase(iter);
- return callback;
+ return callbackIdPair;
}
mEventTriggered = false;
}
- return nullptr;
+ return std::make_pair(nullptr, 0u);
}
VectorAnimationThread::SleepThread::SleepThread(CallbackBase* callback)
#define DALI_TOOLKIT_VECTOR_ANIMATION_THREAD_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @brief Add an event trigger callback.
*
* @param callback The callback to add
+ * @param argument The argument to pass to the callback
* @note Ownership of the callback is NOT passed onto this class.
* @note The callback will be excuted in the main thread.
*/
- void AddEventTriggerCallback(CallbackBase* callback);
+ void AddEventTriggerCallback(CallbackBase* callback, uint32_t argument);
/**
* @brief Remove event trigger callbacks what we added before.
/**
* @brief Gets next event callback to process.
*/
- CallbackBase* GetNextEventCallback();
+ std::pair<CallbackBase*, uint32_t> GetNextEventCallback();
/**
* @brief The thread to sleep until the next frame time.
VectorAnimationThread& operator=(const VectorAnimationThread& thread) = delete;
private:
- std::vector<VectorAnimationTaskPtr> mAnimationTasks;
- std::vector<VectorAnimationTaskPtr> mCompletedTasks;
- std::vector<VectorAnimationTaskPtr> mWorkingTasks;
- std::vector<CallbackBase*> mTriggerEventCallbacks{}; // Callbacks are not owned
- SleepThread mSleepThread;
- ConditionalWait mConditionalWait;
- Mutex mEventTriggerMutex;
- std::unique_ptr<EventThreadCallback> mEventTrigger{};
- bool mNeedToSleep;
- bool mDestroyThread;
- bool mEventTriggered{false};
- const Dali::LogFactoryInterface& mLogFactory;
- const Dali::TraceFactoryInterface& mTraceFactory;
- Dali::AsyncTaskManager mAsyncTaskManager;
+ std::vector<VectorAnimationTaskPtr> mAnimationTasks;
+ std::vector<VectorAnimationTaskPtr> mCompletedTasks;
+ std::vector<VectorAnimationTaskPtr> mWorkingTasks;
+ std::vector<std::pair<CallbackBase*, uint32_t>> mTriggerEventCallbacks{}; // Callbacks are not owned
+ SleepThread mSleepThread;
+ ConditionalWait mConditionalWait;
+ Mutex mEventTriggerMutex;
+ std::unique_ptr<EventThreadCallback> mEventTrigger{};
+ bool mNeedToSleep;
+ bool mDestroyThread;
+ bool mEventTriggered{false};
+ const Dali::LogFactoryInterface& mLogFactory;
+ const Dali::TraceFactoryInterface& mTraceFactory;
+ Dali::AsyncTaskManager mAsyncTaskManager;
};
} // namespace Internal