This is a combination of 8 commits.
[Tizen] (Vector) Use one EventThreadCallback
EventThreadCallback uses an fd internally and some systems have a limit on the number of fd.
So reduce the number of fd
Change-Id: I55f6e7f5211d0bc567a1b2362c782e9fc2a74b90
(Vector) Fix invalid callback issue
Do not execute a callback with lock
Reset callback pointers after removing
Change-Id: I1f617af4ee89e43c8ab891efbd0beaea14feaea1
Keep reference when member callback excute
Change-Id: I406dc8206b6b156e390bab724820dc55e06437f6
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
(Vector) Check adaptor validate before unregister
Change-Id: Iac603db90cee8caf0aefc0f15e536ecb3dd51590
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
(Vector) Erase all EventThreadCallbacks with same pointer
Due to some logical issue, there might be happend that we apply multiple
EventThread callback into VectorAnimationThread.
And if VectorAnimationTask destructor called, we remove only single callback.
If error case occured, EventThread can execute dead callback.
Change-Id: I9e3895180916a292bbca838997ae3053ee8799b5
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
(Vector) To prevent segmentation fault in AnimatedVectorImageVisual
Change-Id: I4c5c94eb9faeffd4727b5879254527692fe93be2
Signed-off-by: seungho baek <sbsh.baek@samsung.com>
(Vector) Use mutex instead of ConditionalWait for EventhThreadCallback
Since worker thread can call AddEventTriggerCallback freely,
we should make some lock between workerthread and
main thread(RemoveEventTriggerCallbacks, GetNextEventCallback, ~VectorAnimationThread).
ConditionalWait used only for AsyncTask rasterization, and sleep thread.
We'd better seperate those job locker.
Change-Id: I543ba7f0bdf54036a8127d1815dda18cafca54e7
Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
(Vector) Change ConditionalWait as Mutex at lottie task
Change-Id: I1f652c440945a9b40a88a992018e14ae0746bc9d
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
Property::Map propertyMap;
propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
- .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
- .Add(DevelImageVisual::Property::LOOP_COUNT, 3);
+ .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME);
Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap);
DALI_TEST_CHECK(visual);
application.GetScene().Add(actor);
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 1 - render a frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+ propertyMap.Clear();
+ propertyMap.Add(DevelImageVisual::Property::LOOP_COUNT, 3);
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, propertyMap);
+
Property::Map attributes;
DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
application.SendNotification();
application.Render();
- // Wait for animation finish - render, finish
- DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+ // Wait for animation finish
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
Property::Map map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
Property::Value* value = map.Find(DevelImageVisual::Property::PLAY_STATE);
Property::Map propertyMap;
propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
.Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
- .Add(DevelImageVisual::Property::LOOP_COUNT, 3)
- .Add(DevelImageVisual::Property::STOP_BEHAVIOR, DevelImageVisual::StopBehavior::FIRST_FRAME)
.Add(ImageVisual::Property::SYNCHRONOUS_LOADING, false);
Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap);
application.GetScene().Add(actor);
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 2 - load & render a frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
+ propertyMap.Clear();
+ propertyMap.Add(DevelImageVisual::Property::LOOP_COUNT, 3);
+ propertyMap.Add(DevelImageVisual::Property::STOP_BEHAVIOR, DevelImageVisual::StopBehavior::FIRST_FRAME);
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, propertyMap);
+
Property::Map attributes;
DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
application.SendNotification();
application.Render();
- // Trigger count is 3 - load, render, animation finished
- DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
+ // Trigger count is 1 - animation finished
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
Property::Map map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
Property::Value* value = map.Find(DevelImageVisual::Property::CURRENT_FRAME_NUMBER);
Property::Map propertyMap;
propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
.Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
- .Add(DevelImageVisual::Property::LOOP_COUNT, 3)
- .Add(DevelImageVisual::Property::STOP_BEHAVIOR, DevelImageVisual::StopBehavior::LAST_FRAME)
- .Add(DevelImageVisual::Property::LOOPING_MODE, DevelImageVisual::LoopingMode::AUTO_REVERSE)
.Add(ImageVisual::Property::SYNCHRONOUS_LOADING, false);
Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap);
application.GetScene().Add(actor);
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 2 - load, render
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
+ propertyMap.Clear();
+ propertyMap.Add(DevelImageVisual::Property::LOOP_COUNT, 3);
+ propertyMap.Add(DevelImageVisual::Property::STOP_BEHAVIOR, DevelImageVisual::StopBehavior::LAST_FRAME);
+ propertyMap.Add(DevelImageVisual::Property::LOOPING_MODE, DevelImageVisual::LoopingMode::AUTO_REVERSE);
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, propertyMap);
+
Property::Map attributes;
DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
application.SendNotification();
application.Render();
- // Trigger count is 3 - load, render, animation finished
- DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
+ // Trigger count is 1 - animation finished
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
Property::Map map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
Property::Value* value = map.Find(DevelImageVisual::Property::CURRENT_FRAME_NUMBER);
application.GetScene().Add(actor1);
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 2 - load & render a frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
propertyMap.Clear();
propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
.Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
application.SendNotification();
application.Render();
- // Trigger count is 4 - load & render a frame for each instance
- DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(4), true, TEST_LOCATION);
+ // Trigger count is 2 - load & render a frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
DevelControl::DoAction(actor2, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, Property::Map());
void AnimatedVectorImageVisual::OnInitialize(void)
{
mVectorAnimationTask->ResourceReadySignal().Connect(this, &AnimatedVectorImageVisual::OnResourceReady);
- mVectorAnimationTask->SetAnimationFinishedCallback(new EventThreadCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnAnimationFinished)));
+ mVectorAnimationTask->SetAnimationFinishedCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnAnimationFinished));
mVectorAnimationTask->KeepRasterizedBuffer(mUseFixedCache);
mVectorAnimationTask->RequestLoad(mUrl.GetUrl(), IsSynchronousLoadingRequired());
void AnimatedVectorImageVisual::OnResourceReady(VectorAnimationTask::ResourceStatus status)
{
+ AnimatedVectorImageVisualPtr self = this; // Keep reference until this API finished
+
if(status == VectorAnimationTask::ResourceStatus::LOADED)
{
if(mImpl->mEventObserver)
void AnimatedVectorImageVisual::OnAnimationFinished()
{
+ 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(mPlayState != DevelImageVisual::PlayState::STOPPED)
}
mEventCallbacks.clear();
- if(mProcessorRegistered)
+ if(mProcessorRegistered && Adaptor::IsAvailable())
{
Adaptor::Get().UnregisterProcessor(*this, true);
}
mVectorRenderer(VectorAnimationRenderer::New()),
mAnimationData(),
mVectorAnimationThread(factoryCache.GetVectorAnimationManager().GetVectorAnimationThread()),
- mConditionalWait(),
+ mMutex(),
mResourceReadySignal(),
- mAnimationFinishedTrigger(),
- mLoadCompletedTrigger(new EventThreadCallback(MakeCallback(this, &VectorAnimationTask::OnLoadCompleted))),
+ mLoadCompletedCallback(MakeCallback(this, &VectorAnimationTask::OnLoadCompleted)),
mPlayState(PlayState::STOPPED),
mStopBehavior(DevelImageVisual::StopBehavior::CURRENT_FRAME),
mLoopingMode(DevelImageVisual::LoopingMode::RESTART),
void VectorAnimationTask::Finalize()
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
-
- // Release some objects in the main thread
- if(mAnimationFinishedTrigger)
- {
- mAnimationFinishedTrigger.reset();
- }
- if(mLoadCompletedTrigger)
{
- mLoadCompletedTrigger.reset();
+ Mutex::ScopedLock lock(mMutex);
+
+ // Release some objects in the main thread
+ if(mAnimationFinishedCallback)
+ {
+ mVectorAnimationThread.RemoveEventTriggerCallbacks(mAnimationFinishedCallback.get());
+ mAnimationFinishedCallback.reset();
+ }
+ if(mLoadCompletedCallback)
+ {
+ mVectorAnimationThread.RemoveEventTriggerCallbacks(mLoadCompletedCallback.get());
+ mLoadCompletedCallback.reset();
+ }
+
+ mDestroyTask = true;
}
mVectorRenderer.Finalize();
-
- mDestroyTask = true;
}
bool VectorAnimationTask::Load(bool synchronousLoading)
{
DALI_LOG_ERROR("VectorAnimationTask::Load: Load failed [%s]\n", mUrl.c_str());
mLoadRequest = false;
- mLoadFailed = true;
- if(!synchronousLoading)
{
- mLoadCompletedTrigger->Trigger();
+ Mutex::ScopedLock lock(mMutex);
+ if(!synchronousLoading && mLoadCompletedCallback)
+ {
+ mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get());
+ }
}
return false;
}
mFrameDurationMicroSeconds = MICROSECONDS_PER_SECOND / mFrameRate;
mLoadRequest = false;
- if(!synchronousLoading)
{
- mLoadCompletedTrigger->Trigger();
+ Mutex::ScopedLock lock(mMutex);
+ if(!synchronousLoading && mLoadCompletedCallback)
+ {
+ mVectorAnimationThread.AddEventTriggerCallback(mLoadCompletedCallback.get());
+ }
}
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationTask::Load: file = %s [%d frames, %f fps] [%p]\n", mUrl.c_str(), mTotalFrame, mFrameRate, this);
void VectorAnimationTask::SetRenderer(Renderer renderer)
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
-
mVectorRenderer.SetRenderer(renderer);
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationTask::SetRenderer [%p]\n", this);
void VectorAnimationTask::SetAnimationData(const AnimationData& data)
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
+ Mutex::ScopedLock lock(mMutex);
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationTask::SetAnimationData [%p]\n", this);
}
}
-void VectorAnimationTask::SetAnimationFinishedCallback(EventThreadCallback* callback)
+void VectorAnimationTask::SetAnimationFinishedCallback(CallbackBase* callback)
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
- if(callback)
- {
- mAnimationFinishedTrigger = std::unique_ptr<EventThreadCallback>(callback);
- }
+ Mutex::ScopedLock lock(mMutex);
+ mAnimationFinishedCallback = std::unique_ptr<CallbackBase>(callback);
}
void VectorAnimationTask::SetLoopCount(int32_t count)
keepAnimation = false;
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
+ Mutex::ScopedLock lock(mMutex);
if(mDestroyTask)
{
// The task will be destroyed. We don't need rasterization.
// Animation is finished
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
- if(mNeedAnimationFinishedTrigger && mAnimationFinishedTrigger)
+ Mutex::ScopedLock lock(mMutex);
+ if(mNeedAnimationFinishedTrigger && mAnimationFinishedCallback)
{
- mAnimationFinishedTrigger->Trigger();
+ mVectorAnimationThread.AddEventTriggerCallback(mAnimationFinishedCallback.get());
}
}
uint32_t index;
{
- ConditionalWait::ScopedLock lock(mConditionalWait);
+ Mutex::ScopedLock lock(mMutex);
if(!mAnimationDataUpdated || mAnimationData[mAnimationDataIndex].resendFlag != 0)
{
#define DALI_TOOLKIT_VECTOR_ANIMATION_TASK_H
/*
- * Copyright (c) 2022 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.
// EXTERNAL INCLUDES
#include <dali/devel-api/adaptor-framework/event-thread-callback.h>
#include <dali/devel-api/adaptor-framework/vector-animation-renderer.h>
-#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/devel-api/threading/mutex.h>
#include <dali/public-api/common/vector-wrapper.h>
#include <dali/public-api/object/property-array.h>
#include <chrono>
* @brief This callback is called after the animation is finished.
* @param[in] callback The animation finished callback
*/
- void SetAnimationFinishedCallback(EventThreadCallback* callback);
+ void SetAnimationFinishedCallback(CallbackBase* callback);
/**
* @brief Gets the playing range in frame number.
VectorAnimationRenderer mVectorRenderer;
AnimationData mAnimationData[2];
VectorAnimationThread& mVectorAnimationThread;
- ConditionalWait mConditionalWait;
+ Mutex mMutex;
ResourceReadySignalType mResourceReadySignal;
- std::unique_ptr<EventThreadCallback> mAnimationFinishedTrigger;
- std::unique_ptr<EventThreadCallback> mLoadCompletedTrigger;
+ std::unique_ptr<CallbackBase> mAnimationFinishedCallback{};
+ std::unique_ptr<CallbackBase> mLoadCompletedCallback{};
PlayState mPlayState;
DevelImageVisual::StopBehavior::Type mStopBehavior;
DevelImageVisual::LoopingMode::Type mLoopingMode;
/*
- * Copyright (c) 2022 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.
mRasterizers(GetNumberOfThreads(NUMBER_OF_RASTERIZE_THREADS_ENV, DEFAULT_NUMBER_OF_RASTERIZE_THREADS), [&]() { return RasterizeHelper(*this); }),
mSleepThread(MakeCallback(this, &VectorAnimationThread::OnAwakeFromSleep)),
mConditionalWait(),
+ mEventTriggerMutex(),
mNeedToSleep(false),
mDestroyThread(false),
mLogFactory(Dali::Adaptor::Get().GetLogFactory())
{
mSleepThread.Start();
+
+ mEventTrigger = std::unique_ptr<EventThreadCallback>(new EventThreadCallback(MakeCallback(this, &VectorAnimationThread::OnEventCallbackTriggered)));
}
VectorAnimationThread::~VectorAnimationThread()
// Stop the thread
{
ConditionalWait::ScopedLock lock(mConditionalWait);
- mDestroyThread = true;
- mNeedToSleep = false;
+ // Wait until some event thread trigger relative job finished.
+ {
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ mDestroyThread = true;
+ }
+ mNeedToSleep = false;
mConditionalWait.Notify(lock);
}
+ // Stop event trigger
+ mEventTrigger.reset();
+
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationThread::~VectorAnimationThread: Join [%p]\n", this);
Join();
}
}
+void VectorAnimationThread::AddEventTriggerCallback(CallbackBase* callback)
+{
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ if(!mDestroyThread)
+ {
+ mTriggerEventCallbacks.push_back(callback);
+
+ if(!mEventTriggered)
+ {
+ mEventTrigger->Trigger();
+ mEventTriggered = true;
+ }
+ }
+}
+
+void VectorAnimationThread::RemoveEventTriggerCallbacks(CallbackBase* callback)
+{
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ if(!mDestroyThread)
+ {
+ auto iter = std::remove(mTriggerEventCallbacks.begin(), mTriggerEventCallbacks.end(), callback);
+ mTriggerEventCallbacks.erase(iter, mTriggerEventCallbacks.end());
+ }
+}
+
void VectorAnimationThread::Run()
{
SetThreadName("VectorAnimationThread");
}
}
+void VectorAnimationThread::OnEventCallbackTriggered()
+{
+ while(CallbackBase* callback = GetNextEventCallback())
+ {
+ CallbackBase::Execute(*callback);
+ }
+}
+
+CallbackBase* VectorAnimationThread::GetNextEventCallback()
+{
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ if(!mDestroyThread)
+ {
+ if(!mTriggerEventCallbacks.empty())
+ {
+ auto iter = mTriggerEventCallbacks.begin();
+ CallbackBase* callback = *iter;
+ mTriggerEventCallbacks.erase(iter);
+ return callback;
+ }
+ mEventTriggered = false;
+ }
+ return nullptr;
+}
+
VectorAnimationThread::RasterizeHelper::RasterizeHelper(VectorAnimationThread& animationThread)
: RasterizeHelper(std::unique_ptr<VectorRasterizeThread>(new VectorRasterizeThread()), animationThread)
{
// EXTERNAL INCLUDES
#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/devel-api/threading/mutex.h>
#include <dali/devel-api/threading/thread.h>
#include <dali/integration-api/adaptor-framework/log-factory-interface.h>
#include <dali/public-api/signals/connection-tracker.h>
~VectorAnimationThread() override;
/**
- * Add a animation task into the vector animation thread, called by main thread.
+ * @brief Add a animation task into the vector animation thread, called by main thread.
*
* @param[in] task The task added to the thread.
*/
/**
* @brief Called when the rasterization is completed from the rasterize thread.
+ *
* @param[in] task The completed task
* @param[in] success true if the task succeeded, false otherwise.
* @param[in] keepAnimation true if the animation is running, false otherwise.
*/
void OnAwakeFromSleep();
+ /**
+ * @brief Add an event trigger callback.
+ *
+ * @param callback The callback to add
+ * @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);
+
+ /**
+ * @brief Remove event trigger callbacks what we added before.
+ *
+ * @param callback The callback to remove
+ */
+ void RemoveEventTriggerCallbacks(CallbackBase* callback);
+
protected:
/**
* @brief The entry function of the animation thread.
private:
/**
- * Rasterizes the tasks.
+ * @brief Rasterizes the tasks.
*/
void Rasterize();
};
/**
+ * @brief Called when the event callback is triggered.
+ */
+ void OnEventCallbackTriggered();
+
+ /**
+ * @brief Gets next event callback to process.
+ */
+ CallbackBase* GetNextEventCallback();
+
+ /**
* @brief The thread to sleep until the next frame time.
*/
class SleepThread : public Thread
std::vector<VectorAnimationTaskPtr> mCompletedTasks;
std::vector<VectorAnimationTaskPtr> mWorkingTasks;
RoundRobinContainerView<RasterizeHelper> mRasterizers;
+ 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;
};