From: Adeel Kazmi Date: Fri, 10 Nov 2023 17:42:46 +0000 (+0000) Subject: Merge "Remove Atlas parameter for TextureManager cache system" into devel/master X-Git-Tag: dali_2.2.53~8 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f6e18b0390831af3b1f12cb6667a4c59256ed2c5;hp=b9d02ab85a00b0a5a95ed531d590a034f0b6b72d;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git Merge "Remove Atlas parameter for TextureManager cache system" into devel/master --- diff --git a/automated-tests/src/dali-physics2d/CMakeLists.txt b/automated-tests/src/dali-physics2d/CMakeLists.txt index e796089..e72db08 100644 --- a/automated-tests/src/dali-physics2d/CMakeLists.txt +++ b/automated-tests/src/dali-physics2d/CMakeLists.txt @@ -16,6 +16,7 @@ SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") SET(TEST_HARNESS_SOURCES ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp ${TEST_HARNESS_DIR}/toolkit-direct-rendering-egl.cpp ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp diff --git a/automated-tests/src/dali-physics3d/CMakeLists.txt b/automated-tests/src/dali-physics3d/CMakeLists.txt index a863fd8..01ecfc0 100644 --- a/automated-tests/src/dali-physics3d/CMakeLists.txt +++ b/automated-tests/src/dali-physics3d/CMakeLists.txt @@ -15,6 +15,7 @@ SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") SET(TEST_HARNESS_SOURCES ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp ${TEST_HARNESS_DIR}/toolkit-direct-rendering-egl.cpp ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp diff --git a/automated-tests/src/dali-scene3d-internal/CMakeLists.txt b/automated-tests/src/dali-scene3d-internal/CMakeLists.txt index 561e753..b050314 100755 --- a/automated-tests/src/dali-scene3d-internal/CMakeLists.txt +++ b/automated-tests/src/dali-scene3d-internal/CMakeLists.txt @@ -26,6 +26,7 @@ SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") SET(TEST_HARNESS_SOURCES ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp diff --git a/automated-tests/src/dali-scene3d/CMakeLists.txt b/automated-tests/src/dali-scene3d/CMakeLists.txt index 1cb9f3c..1269cb2 100755 --- a/automated-tests/src/dali-scene3d/CMakeLists.txt +++ b/automated-tests/src/dali-scene3d/CMakeLists.txt @@ -50,6 +50,7 @@ SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") SET(TEST_HARNESS_SOURCES ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp diff --git a/automated-tests/src/dali-shader-generator/CMakeLists.txt b/automated-tests/src/dali-shader-generator/CMakeLists.txt index 517b7d3..30502d8 100644 --- a/automated-tests/src/dali-shader-generator/CMakeLists.txt +++ b/automated-tests/src/dali-shader-generator/CMakeLists.txt @@ -21,6 +21,7 @@ SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") SET(TEST_HARNESS_SOURCES ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp diff --git a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt index 582d39c..05354c0 100755 --- a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt @@ -61,6 +61,7 @@ ENDIF() SET(TEST_HARNESS_SOURCES ../dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-application.cpp + ../dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-clipboard.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-environment-variable.cpp diff --git a/automated-tests/src/dali-toolkit-styling/CMakeLists.txt b/automated-tests/src/dali-toolkit-styling/CMakeLists.txt index 814a4de..dcce7cd 100644 --- a/automated-tests/src/dali-toolkit-styling/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-styling/CMakeLists.txt @@ -15,6 +15,7 @@ SET(TEST_HARNESS_SOURCES ../dali-toolkit/dali-toolkit-test-utils/test-harness.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-application.cpp + ../dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-clipboard.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-color-controller.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp diff --git a/automated-tests/src/dali-toolkit-third-party/CMakeLists.txt b/automated-tests/src/dali-toolkit-third-party/CMakeLists.txt index 994ec67..9633069 100644 --- a/automated-tests/src/dali-toolkit-third-party/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-third-party/CMakeLists.txt @@ -13,6 +13,7 @@ SET(TC_SOURCES # List of test harness files (Won't get parsed for test cases) SET(TEST_HARNESS_SOURCES ../dali-toolkit/dali-toolkit-test-utils/toolkit-application.cpp + ../dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-clipboard.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp ../dali-toolkit/dali-toolkit-test-utils/toolkit-environment-variable.cpp diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index ec10a33..8d33504 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -100,6 +100,7 @@ SET(TC_SOURCES SET(TEST_HARNESS_SOURCES dali-toolkit-test-utils/toolkit-adaptor.cpp dali-toolkit-test-utils/toolkit-application.cpp + dali-toolkit-test-utils/toolkit-async-task-manager.cpp dali-toolkit-test-utils/toolkit-canvas-renderer.cpp dali-toolkit-test-utils/toolkit-clipboard.cpp dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp index c675fef..f6b8f8b 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,10 @@ Adaptor::Adaptor() Adaptor::~Adaptor() { gAdaptor = nullptr; + + // Ensure all threads and not-excuted tasks are destroyed. + // TODO : we'd better make some singletone service for toolkit UTC in future. + Test::AsyncTaskManager::DestroyAsyncTaskManager(); } void Adaptor::Start(Dali::Window window) diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp new file mode 100644 index 0000000..6678d8d --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.cpp @@ -0,0 +1,1092 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDE +#include +#include +#include + +namespace Dali +{ +namespace Internal +{ +namespace Adaptor +{ +namespace +{ +std::atomic_uint32_t gThreadId = 0u; +} + +class AsyncTaskThread; + +class AsyncTaskManager : public Dali::BaseObject +{ +public: + static Dali::AsyncTaskManager Get(); + + AsyncTaskManager(); + ~AsyncTaskManager(); + +public: + void AddTask(AsyncTaskPtr task); + void RemoveTask(AsyncTaskPtr task); + + Dali::AsyncTaskManager::TasksCompletedId SetCompletedCallback(CallbackBase* callback, Dali::AsyncTaskManager::CompletedCallbackTraceMask mask); + bool RemoveCompletedCallback(Dali::AsyncTaskManager::TasksCompletedId tasksCompletedId); + +public: + AsyncTaskPtr PopNextCompletedTask(); + void TaskCompleted(); + void TaskAllCompleted(); + +public: // Worker thread called method + AsyncTaskPtr PopNextTaskToProcess(); + void CompleteTask(AsyncTaskPtr&& task); + +private: /** + * @brief Helper class to keep the relation between AsyncTaskThread and corresponding container + */ + class TaskHelper + { + public: + /** + * @brief Create an TaskHelper. + * + * @param[in] asyncTaskManager Reference to the AsyncTaskManager + */ + TaskHelper(AsyncTaskManager& asyncTaskManager); + + /** + * @brief Request the thread to process the task. + * @return True if the request succeeds, otherwise false. + */ + bool Request(); + + public: + TaskHelper(const TaskHelper&) = delete; + TaskHelper& operator=(const TaskHelper&) = delete; + + TaskHelper(TaskHelper&& rhs); + TaskHelper& operator=(TaskHelper&& rhs) = delete; + + private: + /** + * @brief Main constructor that used by all other constructors + */ + TaskHelper(std::unique_ptr processor, AsyncTaskManager& asyncTaskManager); + + private: + std::unique_ptr mProcessor; + AsyncTaskManager& mAsyncTaskManager; + }; + + /** + * @brief State of running task + */ + enum class RunningTaskState + { + RUNNING = 0, ///< Running task + CANCELED = 1, ///< Canceled by user + }; + + /** + * @brief State of complete task + */ + enum class CompletedTaskState + { + REQUIRE_CALLBACK = 0, ///< Need to execute callback when completed task process. + SKIP_CALLBACK = 1, ///< Do not execute callback + }; + +private: + // Undefined + AsyncTaskManager(const AsyncTaskManager& manager); + + // Undefined + AsyncTaskManager& operator=(const AsyncTaskManager& manager); + +private: + // Keep Task as list since we take tasks by FIFO as default. + using AsyncTaskContainer = std::list; + + using AsyncRunningTaskPair = std::pair; + using AsyncRunningTaskContainer = std::list; + + using AsyncCompletedTaskPair = std::pair; + using AsyncCompletedTaskContainer = std::list; + + AsyncTaskContainer mWaitingTasks; ///< The queue of the tasks waiting to async process. Must be locked under mWaitingTasksMutex. + AsyncRunningTaskContainer mRunningTasks; ///< The queue of the running tasks. Must be locked under mRunningTasksMutex. + AsyncCompletedTaskContainer mCompletedTasks; ///< The queue of the tasks with the async process. Must be locked under mCompletedTasksMutex. + + RoundRobinContainerView mTasks; + + Dali::Mutex mWaitingTasksMutex; ///< Mutex for mWaitingTasks. We can lock mRunningTasksMutex and mCompletedTasksMutex under this scope. + Dali::Mutex mRunningTasksMutex; ///< Mutex for mRunningTasks. We can lock mCompletedTasksMutex under this scope. + Dali::Mutex mCompletedTasksMutex; ///< Mutex for mCompletedTasks. We cannot lock any mutex under this scope. + + std::unique_ptr mTrigger; + + struct TasksCompletedImpl; + std::unique_ptr mTasksCompletedImpl; ///< TaskS completed signal interface for AsyncTaskManager. +}; + +inline Internal::Adaptor::AsyncTaskManager& GetImplementation(Dali::AsyncTaskManager& obj) +{ + DALI_ASSERT_ALWAYS(obj && "AsyncTaskManager is empty"); + + Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(handle); +} + +inline const Internal::Adaptor::AsyncTaskManager& GetImplementation(const Dali::AsyncTaskManager& obj) +{ + DALI_ASSERT_ALWAYS(obj && "AsyncTaskManager is empty"); + + const Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(handle); +} + +/********************************************************************************/ +/********************************* INTERNAL CLASS *****************************/ +/********************************************************************************/ + +namespace +{ +constexpr auto FORCE_TRIGGER_THRESHOLD = 128u; ///< Trigger TasksCompleted() forcely if the number of completed task contain too much. + +constexpr auto DEFAULT_NUMBER_OF_ASYNC_THREADS = size_t{8u}; +constexpr auto NUMBER_OF_ASYNC_THREADS_ENV = "DALI_ASYNC_MANAGER_THREAD_POOL_SIZE"; + +size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue) +{ + auto numberString = EnvironmentVariable::GetEnvironmentVariable(environmentVariable); + auto numberOfThreads = numberString ? std::strtoul(numberString, nullptr, 10) : 0; + constexpr auto MAX_NUMBER_OF_THREADS = 16u; + DALI_ASSERT_DEBUG(numberOfThreads <= MAX_NUMBER_OF_THREADS); + return (numberOfThreads > 0 && numberOfThreads <= MAX_NUMBER_OF_THREADS) ? numberOfThreads : defaultValue; +} + +thread_local Dali::AsyncTaskManager gAsyncTaskManager; +} // namespace + +/** + * The worker thread for async process + */ +class AsyncTaskThread : public Thread +{ +public: + /** + * Constructor. + */ + AsyncTaskThread(AsyncTaskManager& asyncTaskManager) + : mConditionalWait(), + mAsyncTaskManager(asyncTaskManager), + mThreadId(++gThreadId), + mDestroyThread(false), + mIsThreadStarted(false), + mIsThreadIdle(true) + { + } + + /** + * Destructor. + */ + ~AsyncTaskThread() override + { + // Stop the thread + { + ConditionalWait::ScopedLock lock(mConditionalWait); + mDestroyThread = true; + mConditionalWait.Notify(lock); + } + + Join(); + } + + /** + * @brief Request the thread to process the task. + * @return True if the request is successed, otherwise false. + */ + bool Request() + { + if(!mIsThreadStarted) + { + Start(); + mIsThreadStarted = true; + } + + { + // Lock while adding task to the queue + ConditionalWait::ScopedLock lock(mConditionalWait); + + if(mIsThreadIdle) + { + mIsThreadIdle = false; + + // wake up the thread + mConditionalWait.Notify(lock); + return true; + } + } + + return false; + } + +protected: + /** + * The entry function of the worker thread. + */ + void Run() override + { + { + char temp[100]; + snprintf(temp, 100, "AsyncTaskThread[%u]", mThreadId); + SetThreadName(temp); + } + + while(!mDestroyThread) + { + AsyncTaskPtr task = mAsyncTaskManager.PopNextTaskToProcess(); + if(!task) + { + ConditionalWait::ScopedLock lock(mConditionalWait); + if(!mDestroyThread) + { + mIsThreadIdle = true; + mConditionalWait.Wait(lock); + } + } + else + { + task->Process(); + if(!mDestroyThread) + { + mAsyncTaskManager.CompleteTask(std::move(task)); + } + } + } + } + +private: + // Undefined + AsyncTaskThread(const AsyncTaskThread& thread) = delete; + + // Undefined + AsyncTaskThread& operator=(const AsyncTaskThread& thread) = delete; + +private: + ConditionalWait mConditionalWait; + AsyncTaskManager& mAsyncTaskManager; + uint32_t mThreadId; + bool mDestroyThread; + bool mIsThreadStarted; + bool mIsThreadIdle; +}; + +// AsyncTaskManager::TasksCompletedImpl + +struct AsyncTaskManager::TasksCompletedImpl +{ + TasksCompletedImpl(AsyncTaskManager& manager, EventThreadCallback* trigger) + : mManager(manager), + mTrigger(trigger), + mEmitCompletedTaskTriggered(false) + { + } + +public: + /** + * @brief Create new tasks completed id and. + * @post AppendTaskTrace or CheckTasksCompletedCallbackCompleted should be called. + * @param[in] callback The callback that want to be executed when we notify that all tasks completed. + */ + Dali::AsyncTaskManager::TasksCompletedId GenerateTasksCompletedId(CallbackBase* callback) + { + // Lock while adding tasks completed callback list to the queue + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + + auto id = mTasksCompletedCount++; + DALI_ASSERT_ALWAYS(mTasksCompletedCallbackList.find(id) == mTasksCompletedCallbackList.end()); + + mTasksCompletedCallbackList.insert({id, CallbackData(callback)}); + return id; + } + + /** + * @brief Append task that will be trace. + * @post RemoveTaskTrace should be called. + * @param[in] id The id of tasks completed. + * @param[in] task The task want to trace. + */ + void AppendTaskTrace(Dali::AsyncTaskManager::TasksCompletedId id, AsyncTaskPtr task) + { + // Lock while adding tasks completed callback list to the queue + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + + auto iter = mTasksCompletedCallbackList.find(id); + if(iter == mTasksCompletedCallbackList.end()) + { + // This task is already erased. Ignore. + return; + } + + auto& callbackData = iter->second; + + auto jter = callbackData.mTasks.find(task.Get()); + + if(jter != callbackData.mTasks.end()) + { + // Increase reference count. + ++(jter->second); + } + else + { + callbackData.mTasks.insert({task.Get(), 1u}); + } + } + + /** + * @brief Remove all task that were traced. + * @param[in] task The task want to remove trace. + * @param[in] taskCount The number of tasks that will be removed. + */ + void RemoveTaskTrace(AsyncTaskPtr task, uint32_t count = 1u) + { + if(count == 0u) + { + return; + } + + // Lock while removing tasks completed callback list to the queue + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + + for(auto iter = mTasksCompletedCallbackList.begin(); iter != mTasksCompletedCallbackList.end();) + { + auto& callbackData = iter->second; + bool eraseCallbackData = false; + + auto jter = callbackData.mTasks.find(task.Get()); + + if(jter != callbackData.mTasks.end()) + { + if(jter->second <= count) + { + callbackData.mTasks.erase(jter); + + if(callbackData.mTasks.empty()) + { + eraseCallbackData = true; + + // Move callback base into list. + // (To avoid task container changed during callback emit) + RegisterTasksCompletedCallback(std::move(callbackData.mCallback), iter->first); + + iter = mTasksCompletedCallbackList.erase(iter); + } + } + else + { + jter->second -= count; + } + } + + if(!eraseCallbackData) + { + ++iter; + } + } + } + + /** + * @brief Check whether current TasksCompletedId completed or not. + * @param[in] id The id of tasks completed. + * @return True if all tasks are completed so we need to execute callback soon. False otherwise. + */ + bool CheckTasksCompletedCallbackCompleted(Dali::AsyncTaskManager::TasksCompletedId id) + { + // Lock while removing tasks completed callback list to the queue + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + + auto iter = mTasksCompletedCallbackList.find(id); + if(iter != mTasksCompletedCallbackList.end()) + { + auto& callbackData = iter->second; + if(callbackData.mTasks.empty()) + { + // Move callback base into list. + // (To avoid task container changed during callback emit) + RegisterTasksCompletedCallback(std::move(callbackData.mCallback), iter->first); + + iter = mTasksCompletedCallbackList.erase(iter); + + return true; + } + } + + return false; + } + + /** + * @brief Remove taskS completed callbacks by id. + * @param[in] id The id of taskS completed. + * @return True if taskS completed id removed. False otherwise. + */ + bool RemoveTasksCompleted(Dali::AsyncTaskManager::TasksCompletedId id) + { + // Lock while removing taskS completed callback list to the queue + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + + auto iter = mTasksCompletedCallbackList.find(id); + if(iter == mTasksCompletedCallbackList.end()) + { + // This task is already erased, or completed. + // Erase from completed excute callback list. + + // Lock while removing excute callback list to the queue + Mutex::ScopedLock lock(mExcuteCallbacksMutex); + + for(auto iter = mExcuteCallbackList.begin(); iter != mExcuteCallbackList.end();) + { + if(iter->second == id) + { + iter = mExcuteCallbackList.erase(iter); + + return true; + } + else + { + ++iter; + } + } + + // This task is alread erased and completed. Ignore. + return false; + } + + mTasksCompletedCallbackList.erase(iter); + + return true; + } + + /** + * @brief Emit all completed callbacks. + * @note This API should be called at event thread. + */ + void EmitCompletedTasks() + { + ExecuteCallbackContainer executeCallbackList; + { + // Lock while removing excute callback list to the queue + Mutex::ScopedLock lock(mExcuteCallbacksMutex); + + mEmitCompletedTaskTriggered = false; + + // Copy callback lists, for let we execute callbacks out of mutex + executeCallbackList = std::move(mExcuteCallbackList); + mExcuteCallbackList.clear(); + } + + if(!executeCallbackList.empty()) + { + // Execute all callbacks + for(auto&& callbackPair : executeCallbackList) + { + auto& callback = callbackPair.first; + auto id = callbackPair.second; + + Dali::CallbackBase::Execute(*callback, id); + } + } + } + + /** + * @brief Check whether there is some completed signal what we need to trace, or not. + * @return True if mTasksCompletedCallbackList is not empty. False otherwise. + */ + bool IsTasksCompletedCallbackExist() + { + Mutex::ScopedLock lock(mTasksCompletedCallbacksMutex); + return !mTasksCompletedCallbackList.empty(); + } + + /** + * @brief Check whether there is some completed signal what we need to execute, or not. + * @return True if mExcuteCallbackList is not empty. False otherwise. + */ + bool IsExecuteCallbackExist() + { + Mutex::ScopedLock lock(mExcuteCallbacksMutex); + return !mExcuteCallbackList.empty(); + } + +private: + void RegisterTasksCompletedCallback(std::unique_ptr callback, Dali::AsyncTaskManager::TasksCompletedId id) + { + // Lock while adding excute callback list to the queue + Mutex::ScopedLock lock(mExcuteCallbacksMutex); + + mExcuteCallbackList.emplace_back(std::move(callback), id); + + if(!mEmitCompletedTaskTriggered) + { + mEmitCompletedTaskTriggered = true; + mTrigger->Trigger(); + } + } + +private: + struct CallbackData + { + public: + CallbackData(CallbackBase* callback) + : mCallback(callback), + mTasks() + { + } + + CallbackData(CallbackData&& rhs) noexcept + : mCallback(std::move(rhs.mCallback)), + mTasks(std::move(rhs.mTasks)) + { + } + + CallbackData& operator=(CallbackData&& rhs) noexcept + { + if(this != &rhs) + { + mCallback = std::move(rhs.mCallback); + mTasks = std::move(rhs.mTasks); + } + + return *this; + } + + private: + // Delete copy operator. + CallbackData(const CallbackData& rhs) = delete; + CallbackData& operator=(const CallbackData& rhs) = delete; + + public: + std::unique_ptr mCallback; + std::unordered_map mTasks; + }; + +private: + AsyncTaskManager& mManager; ///< Owner of this CacheImpl. + EventThreadCallback* mTrigger; ///< EventThread callback trigger. (Not owned.) + + Dali::AsyncTaskManager::TasksCompletedId mTasksCompletedCount{0u}; + + using TasksCompletedContainer = std::unordered_map; + TasksCompletedContainer mTasksCompletedCallbackList; + + using ExecuteCallbackContainer = std::vector, Dali::AsyncTaskManager::TasksCompletedId>>; + ExecuteCallbackContainer mExcuteCallbackList; + + Dali::Mutex mTasksCompletedCallbacksMutex; ///< Mutex for mTasksCompletedCallbackList. We can lock mExcuteCallbacksMutex under this scope. + Dali::Mutex mExcuteCallbacksMutex; ///< Mutex for mExcuteCallbackList. + + bool mEmitCompletedTaskTriggered : 1; +}; + +// AsyncTaskManager + +Dali::AsyncTaskManager AsyncTaskManager::Get() +{ + if(!gAsyncTaskManager) + { + gAsyncTaskManager = Dali::AsyncTaskManager(new AsyncTaskManager()); + } + return gAsyncTaskManager; +} + +AsyncTaskManager::AsyncTaskManager() +: mTasks(GetNumberOfThreads(NUMBER_OF_ASYNC_THREADS_ENV, DEFAULT_NUMBER_OF_ASYNC_THREADS), [&]() { return TaskHelper(*this); }), + mTrigger(new EventThreadCallback(MakeCallback(this, &AsyncTaskManager::TaskCompleted))), + mTasksCompletedImpl(new TasksCompletedImpl(*this, mTrigger.get())) +{ +} + +AsyncTaskManager::~AsyncTaskManager() +{ + // Join all threads. + mTasks.Clear(); + + // Remove task completed impl after all threads are join. + mTasksCompletedImpl.reset(); + + // Remove tasks + mWaitingTasks.clear(); + mRunningTasks.clear(); + mCompletedTasks.clear(); +} + +void AsyncTaskManager::AddTask(AsyncTaskPtr task) +{ + if(task) + { + // Lock while adding task to the queue + Mutex::ScopedLock lock(mWaitingTasksMutex); + + // push back into waiting queue. + mWaitingTasks.insert(mWaitingTasks.end(), task); + + { + // For thread safety + Mutex::ScopedLock lock(mRunningTasksMutex); // We can lock this mutex under mWaitingTasksMutex. + + // Finish all Running threads are working + if(mRunningTasks.size() >= mTasks.GetElementCount()) + { + return; + } + } + } + + size_t count = mTasks.GetElementCount(); + size_t index = 0; + while(index++ < count) + { + auto processHelperIt = mTasks.GetNext(); + DALI_ASSERT_ALWAYS(processHelperIt != mTasks.End()); + if(processHelperIt->Request()) + { + break; + } + // If all threads are busy, then it's ok just to push the task because they will try to get the next job. + } +} + +void AsyncTaskManager::RemoveTask(AsyncTaskPtr task) +{ + if(task) + { + uint32_t removedCount = 0u; + + { + // Lock while remove task from the queue + Mutex::ScopedLock lock(mWaitingTasksMutex); + + for(auto iterator = mWaitingTasks.begin(); iterator != mWaitingTasks.end();) + { + if((*iterator) == task) + { + iterator = mWaitingTasks.erase(iterator); + ++removedCount; + } + else + { + ++iterator; + } + } + } + + { + // Lock while remove task from the queue + Mutex::ScopedLock lock(mRunningTasksMutex); + + for(auto iterator = mRunningTasks.begin(); iterator != mRunningTasks.end();) + { + if((*iterator).first == task) + { + // We cannot erase container. Just mark as canceled. + // Note : mAvaliableLowPriorityTaskCounts will be increased after process finished. + (*iterator).second = RunningTaskState::CANCELED; + ++removedCount; + } + ++iterator; + } + } + + { + // Lock while remove task from the queue + Mutex::ScopedLock lock(mCompletedTasksMutex); + + for(auto iterator = mCompletedTasks.begin(); iterator != mCompletedTasks.end();) + { + if((*iterator).first == task) + { + iterator = mCompletedTasks.erase(iterator); + ++removedCount; + } + else + { + ++iterator; + } + } + } + + // Remove TasksCompleted callback trace + if(mTasksCompletedImpl->IsTasksCompletedCallbackExist() && removedCount > 0u) + { + mTasksCompletedImpl->RemoveTaskTrace(task, removedCount); + } + } +} + +Dali::AsyncTaskManager::TasksCompletedId AsyncTaskManager::SetCompletedCallback(CallbackBase* callback, Dali::AsyncTaskManager::CompletedCallbackTraceMask mask) +{ + // mTasksCompletedImpl will take ownership of callback. + Dali::AsyncTaskManager::TasksCompletedId tasksCompletedId = mTasksCompletedImpl->GenerateTasksCompletedId(callback); + + bool taskAdded = false; ///< Flag whether at least one task tracing now. + + // Please be careful the order of mutex, to avoid dead lock. + { + Mutex::ScopedLock lockWait(mWaitingTasksMutex); + { + Mutex::ScopedLock lockRunning(mRunningTasksMutex); // We can lock this mutex under mWaitingTasksMutex. + { + Mutex::ScopedLock lockComplete(mCompletedTasksMutex); // We can lock this mutex under mWaitingTasksMutex and mRunningTasksMutex. + + // Collect all tasks from waiting tasks + for(auto& task : mWaitingTasks) + { + auto checkMask = (task->GetCallbackInvocationThread() == Dali::AsyncTask::ThreadType::MAIN_THREAD ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_MAIN : Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_WORKER) | + (task->GetPriorityType() == Dali::AsyncTask::PriorityType::HIGH ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_HIGH : Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_LOW); + + if((checkMask & mask) == checkMask) + { + taskAdded = true; + mTasksCompletedImpl->AppendTaskTrace(tasksCompletedId, task); + } + } + + // Collect all tasks from running tasks + for(auto& taskPair : mRunningTasks) + { + auto& task = taskPair.first; + auto checkMask = (task->GetCallbackInvocationThread() == Dali::AsyncTask::ThreadType::MAIN_THREAD ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_MAIN : Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_WORKER) | + (task->GetPriorityType() == Dali::AsyncTask::PriorityType::HIGH ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_HIGH : Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_LOW); + + if((checkMask & mask) == checkMask) + { + taskAdded = true; + mTasksCompletedImpl->AppendTaskTrace(tasksCompletedId, task); + } + } + + // Collect all tasks from complete tasks + for(auto& taskPair : mCompletedTasks) + { + auto& task = taskPair.first; + auto checkMask = (task->GetCallbackInvocationThread() == Dali::AsyncTask::ThreadType::MAIN_THREAD ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_MAIN : Dali::AsyncTaskManager::CompletedCallbackTraceMask::THREAD_MASK_WORKER) | + (task->GetPriorityType() == Dali::AsyncTask::PriorityType::HIGH ? Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_HIGH : Dali::AsyncTaskManager::CompletedCallbackTraceMask::PRIORITY_MASK_LOW); + + if((checkMask & mask) == checkMask) + { + taskAdded = true; + mTasksCompletedImpl->AppendTaskTrace(tasksCompletedId, task); + } + } + } + } + } + + // If there is nothing to check task, just excute callback right now. + if(!taskAdded) + { + mTasksCompletedImpl->CheckTasksCompletedCallbackCompleted(tasksCompletedId); + } + return tasksCompletedId; +} + +bool AsyncTaskManager::RemoveCompletedCallback(Dali::AsyncTaskManager::TasksCompletedId tasksCompletedId) +{ + return mTasksCompletedImpl->RemoveTasksCompleted(tasksCompletedId); +} + +AsyncTaskPtr AsyncTaskManager::PopNextCompletedTask() +{ + std::vector ignoredTaskList; ///< To keep asyncTask reference so we can ensure that destructor called out of mutex. + + AsyncTaskPtr nextCompletedTask = nullptr; + { + // Lock while popping task out from the queue + Mutex::ScopedLock lock(mCompletedTasksMutex); + + while(!mCompletedTasks.empty()) + { + auto next = mCompletedTasks.begin(); + AsyncTaskPtr nextTask = next->first; + CompletedTaskState taskState = next->second; + mCompletedTasks.erase(next); + + if(taskState == CompletedTaskState::REQUIRE_CALLBACK) + { + nextCompletedTask = nextTask; + break; + } + + ignoredTaskList.push_back(nextTask); + } + } + + return nextCompletedTask; +} + +void AsyncTaskManager::TaskCompleted() +{ + // For UTC, let we complete only 1 task here. + if(AsyncTaskPtr task = PopNextCompletedTask()) + { + CallbackBase::Execute(*(task->GetCompletedCallback()), task); + + // Remove TasksCompleted callback trace + if(mTasksCompletedImpl->IsTasksCompletedCallbackExist()) + { + mTasksCompletedImpl->RemoveTaskTrace(task); + } + } + + mTasksCompletedImpl->EmitCompletedTasks(); +} + +void AsyncTaskManager::TaskAllCompleted() +{ + while(AsyncTaskPtr task = PopNextCompletedTask()) + { + CallbackBase::Execute(*(task->GetCompletedCallback()), task); + + // Remove TasksCompleted callback trace + if(mTasksCompletedImpl->IsTasksCompletedCallbackExist()) + { + mTasksCompletedImpl->RemoveTaskTrace(task); + } + } + + mTasksCompletedImpl->EmitCompletedTasks(); +} + +/// Worker thread called +AsyncTaskPtr AsyncTaskManager::PopNextTaskToProcess() +{ + // Lock while popping task out from the queue + Mutex::ScopedLock lock(mWaitingTasksMutex); + + // pop out the next task from the queue + AsyncTaskPtr nextTask = nullptr; + + for(auto iter = mWaitingTasks.begin(), endIter = mWaitingTasks.end(); iter != endIter; ++iter) + { + if((*iter)->IsReady()) + { + nextTask = *iter; + + // Add Running queue + { + // Lock while popping task out from the queue + Mutex::ScopedLock lock(mRunningTasksMutex); // We can lock this mutex under mWaitingTasksMutex. + + mRunningTasks.insert(mRunningTasks.end(), std::make_pair(nextTask, RunningTaskState::RUNNING)); + + mWaitingTasks.erase(iter); + } + break; + } + } + + return nextTask; +} + +/// Worker thread called +void AsyncTaskManager::CompleteTask(AsyncTaskPtr&& task) +{ + bool notify = false; + + if(task) + { + bool needTrigger = false; + + // Lock while check validation of task. + { + Mutex::ScopedLock lock(mRunningTasksMutex); + + auto iter = std::find_if(mRunningTasks.begin(), mRunningTasks.end(), [task](const AsyncRunningTaskPair& element) { return element.first == task; }); + if(iter != mRunningTasks.end()) + { + if(iter->second == RunningTaskState::RUNNING) + { + // This task is valid. + notify = true; + } + } + } + + // We should execute this tasks complete callback out of mutex + if(notify && task->GetCallbackInvocationThread() == AsyncTask::ThreadType::WORKER_THREAD) + { + CallbackBase::Execute(*(task->GetCompletedCallback()), task); + + // We need to remove task trace now. + if(mTasksCompletedImpl->IsTasksCompletedCallbackExist()) + { + mTasksCompletedImpl->RemoveTaskTrace(task); + + if(mTasksCompletedImpl->IsExecuteCallbackExist()) + { + // We need to call EmitCompletedTasks(). Trigger main thread. + needTrigger = true; + } + } + } + + // Lock while adding task to the queue + { + Mutex::ScopedLock lock(mRunningTasksMutex); + + auto iter = std::find_if(mRunningTasks.begin(), mRunningTasks.end(), [task](const AsyncRunningTaskPair& element) { return element.first == task; }); + if(iter != mRunningTasks.end()) + { + // Move task into completed, for ensure that AsyncTask destroy at main thread. + { + Mutex::ScopedLock lock(mCompletedTasksMutex); // We can lock this mutex under mRunningTasksMutex. + + const bool callbackRequired = notify && (task->GetCallbackInvocationThread() == AsyncTask::ThreadType::MAIN_THREAD); + + needTrigger |= callbackRequired; + + mCompletedTasks.insert(mCompletedTasks.end(), std::make_pair(task, callbackRequired ? CompletedTaskState::REQUIRE_CALLBACK : CompletedTaskState::SKIP_CALLBACK)); + + mRunningTasks.erase(iter); + + if(!needTrigger) + { + needTrigger |= (mCompletedTasks.size() >= FORCE_TRIGGER_THRESHOLD); + } + + // Now, task is invalidate. + task.Reset(); + } + } + } + + // Wake up the main thread + if(needTrigger) + { + mTrigger->Trigger(); + } + } +} + +// AsyncTaskManager::TaskHelper + +AsyncTaskManager::TaskHelper::TaskHelper(AsyncTaskManager& asyncTaskManager) +: TaskHelper(std::unique_ptr(new AsyncTaskThread(asyncTaskManager)), asyncTaskManager) +{ +} + +AsyncTaskManager::TaskHelper::TaskHelper(TaskHelper&& rhs) +: TaskHelper(std::move(rhs.mProcessor), rhs.mAsyncTaskManager) +{ +} + +AsyncTaskManager::TaskHelper::TaskHelper(std::unique_ptr processor, AsyncTaskManager& asyncTaskManager) +: mProcessor(std::move(processor)), + mAsyncTaskManager(asyncTaskManager) +{ +} + +bool AsyncTaskManager::TaskHelper::Request() +{ + return mProcessor->Request(); +} + +} // namespace Adaptor + +} // namespace Internal + +/********************************************************************************/ +/********************************* PUBLIC CLASS *******************************/ +/********************************************************************************/ + +AsyncTaskManager::AsyncTaskManager() = default; + +AsyncTaskManager::~AsyncTaskManager() = default; + +AsyncTaskManager AsyncTaskManager::Get() +{ + return Internal::Adaptor::AsyncTaskManager::Get(); +} + +void AsyncTaskManager::AddTask(AsyncTaskPtr task) +{ + Internal::Adaptor::GetImplementation(*this).AddTask(task); +} + +void AsyncTaskManager::RemoveTask(AsyncTaskPtr task) +{ + Internal::Adaptor::GetImplementation(*this).RemoveTask(task); +} + +AsyncTaskManager::TasksCompletedId AsyncTaskManager::SetCompletedCallback(CallbackBase* callback, AsyncTaskManager::CompletedCallbackTraceMask mask) +{ + return Internal::Adaptor::GetImplementation(*this).SetCompletedCallback(callback, mask); +} + +bool AsyncTaskManager::RemoveCompletedCallback(AsyncTaskManager::TasksCompletedId tasksCompletedId) +{ + return Internal::Adaptor::GetImplementation(*this).RemoveCompletedCallback(tasksCompletedId); +} + +AsyncTaskManager::AsyncTaskManager(Internal::Adaptor::AsyncTaskManager* impl) +: BaseHandle(impl) +{ +} + +} // namespace Dali + +namespace Test +{ +namespace AsyncTaskManager +{ +void DestroyAsyncTaskManager() +{ + Dali::Internal::Adaptor::gAsyncTaskManager.Reset(); +} + +void ProcessSingleCompletedTask() +{ + auto asyncTaskManager = Dali::AsyncTaskManager::Get(); + Dali::Internal::Adaptor::GetImplementation(asyncTaskManager).TaskCompleted(); +} + +void ProcessAllCompletedTask() +{ + auto asyncTaskManager = Dali::AsyncTaskManager::Get(); + Dali::Internal::Adaptor::GetImplementation(asyncTaskManager).TaskAllCompleted(); +} +} // namespace AsyncTaskManager +} // namespace Test \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.h b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.h new file mode 100644 index 0000000..875daaf --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-async-task-manager.h @@ -0,0 +1,39 @@ +#ifndef DALI_TOOLKIT_TOOLKIT_ASYNC_TASK_MANAGER_H +#define DALI_TOOLKIT_TOOLKIT_ASYNC_TASK_MANAGER_H + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// EXTERNAL INCLUDES +#include + +namespace Test +{ +namespace AsyncTaskManager +{ +// Destroy global static AsyncTaskManager, only for ~ToolkitApplication() +void DestroyAsyncTaskManager(); + +// Execute one completed processes synchronously. +void ProcessSingleCompletedTasks(); + +// Execute all completed processes synchronously. +void ProcessAllCompletedTasks(); +} // namespace AsyncTaskManager +} // namespace Test + +#endif // DALI_TOOLKIT_TOOLKIT_ASYNC_TASK_MANAGER_H diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp index 29abcd2..73f4cf7 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp @@ -1233,10 +1233,9 @@ int UtcDaliAnimatedImageVisualMultiImage01(void) tet_infoline("Test that after 2 ticks that we have 6 textures"); Test::EmitGlobalTimerSignal(); - // TODO : Open this logic if we make AsyncTaskManager for toolkit UTC doesn't execute by SendNotification(). - //application.SendNotification(); - //application.Render(16); - //DALI_TEST_EQUALS(gl.GetNumGeneratedTextures(), 6, TEST_LOCATION); + application.SendNotification(); + application.Render(16); + DALI_TEST_EQUALS(gl.GetNumGeneratedTextures(), 6, TEST_LOCATION); tet_infoline("And that at least 2 textures were requested"); DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); @@ -1246,10 +1245,9 @@ int UtcDaliAnimatedImageVisualMultiImage01(void) tet_infoline("Test that after 3rd tick that we have 7 textures and 1 request"); Test::EmitGlobalTimerSignal(); - // TODO : Open this logic if we make AsyncTaskManager for toolkit UTC doesn't execute by SendNotification(). - //application.SendNotification(); - //application.Render(16); - //DALI_TEST_EQUALS(gl.GetNumGeneratedTextures(), 7, TEST_LOCATION); + application.SendNotification(); + application.Render(16); + DALI_TEST_EQUALS(gl.GetNumGeneratedTextures(), 7, TEST_LOCATION); DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); application.SendNotification(); diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp index 907b8af..2aa3d4e 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp @@ -1049,6 +1049,51 @@ int UtcDaliAnimatedVectorImageVisualMarkerInfo(void) END_TEST; } +int UtcDaliAnimatedVectorImageVisualMarkerInfoFromInvalid(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliAnimatedVectorImageVisualMarkerInfoFromInvalid"); + + Property::Map propertyMap; + propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE) + .Add(ImageVisual::Property::URL, "invalid.json") + .Add(ImageVisual::Property::SYNCHRONOUS_LOADING, false); + + Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + DummyControl actor = DummyControl::New(true); + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual); + + Vector2 controlSize(20.f, 30.f); + actor.SetProperty(Actor::Property::SIZE, controlSize); + + application.GetScene().Add(actor); + + Property::Map attributes; + DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes); + + application.SendNotification(); + application.Render(); + + // Trigger count is 1 - load, and failed. + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // renderer is added to actor + DALI_TEST_CHECK(actor.GetRendererCount() == 1u); + Renderer renderer = actor.GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + Property::Map map = actor.GetProperty(DummyControl::Property::TEST_VISUAL); + Property::Value* value = map.Find(DevelImageVisual::Property::MARKER_INFO); + + // The values when load failed case is invalid. + DALI_TEST_CHECK(value == nullptr || (value->GetMap() == nullptr) || (value->GetMap()->Empty())); + + END_TEST; +} + int UtcDaliAnimatedVectorImageVisualAnimationFinishedSignal(void) { ToolkitTestApplication application; diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp index 5ca6052..52d5495 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp @@ -4389,7 +4389,13 @@ int UtcDaliImageViewSetImageOnResourceReadySignal06(void) DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); DALI_TEST_EQUALS(gResourceReadySignalCounter, 2, TEST_LOCATION); - tet_infoline("load done"); + tet_infoline("Note that resource ready should not come now."); + tet_infoline("Try to load remained 2 valid image + apply masking"); + + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(4), true, TEST_LOCATION); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 4, TEST_LOCATION); + + tet_infoline("Check all resource ready comes now."); } END_TEST; } diff --git a/dali-scene3d/public-api/algorithm/path-finder-waypoint.h b/dali-scene3d/public-api/algorithm/path-finder-waypoint.h index ec07a5e..94c2b71 100644 --- a/dali-scene3d/public-api/algorithm/path-finder-waypoint.h +++ b/dali-scene3d/public-api/algorithm/path-finder-waypoint.h @@ -29,7 +29,7 @@ namespace Dali::Scene3D::Internal::Algorithm { -class WayPointData; +struct WayPointData; } namespace Dali::Scene3D::Algorithm diff --git a/dali-toolkit/internal/texture-manager/texture-manager-impl.cpp b/dali-toolkit/internal/texture-manager/texture-manager-impl.cpp index 5b8a154..d9f8f0f 100644 --- a/dali-toolkit/internal/texture-manager/texture-manager-impl.cpp +++ b/dali-toolkit/internal/texture-manager/texture-manager-impl.cpp @@ -987,6 +987,9 @@ void TextureManager::ProcessLoadQueue() { if(element.mObserver) { + DALI_LOG_INFO(gTextureManagerLogFilter, Debug::Verbose, " Disconnect DestructionSignal to observer:%p\n", element.mObserver); + element.mObserver->DestructionSignal().Disconnect(this, &TextureManager::ObserverDestroyed); + EmitLoadComplete(element.mObserver, textureInfo, true); } } diff --git a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.cpp b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.cpp index 50e6471..59cd4ba 100644 --- a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.cpp +++ b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.cpp @@ -51,7 +51,7 @@ VectorAnimationManager::~VectorAnimationManager() { mEventCallbacks.clear(); - if(mProcessorRegistered) + if(mProcessorRegistered && Adaptor::IsAvailable()) { Adaptor::Get().UnregisterProcessor(*this, true); } diff --git a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp index c0d3802..4bb2471 100644 --- a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp +++ b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp @@ -63,6 +63,8 @@ VectorAnimationTask::VectorAnimationTask(VisualFactoryCache& factoryCache) mConditionalWait(), mResourceReadySignal(), mLoadCompletedCallback(MakeCallback(this, &VectorAnimationTask::OnLoadCompleted)), + mCachedLayerInfo(), + mCachedMarkerInfo(), mPlayState(PlayState::STOPPED), mStopBehavior(DevelImageVisual::StopBehavior::CURRENT_FRAME), mLoopingMode(DevelImageVisual::LoopingMode::RESTART), @@ -87,7 +89,9 @@ VectorAnimationTask::VectorAnimationTask(VisualFactoryCache& factoryCache) mLoadRequest(false), mLoadFailed(false), mRasterized(false), - mKeepAnimation(false) + mKeepAnimation(false), + mLayerInfoCached(false), + mMarkerInfoCached(false) { mVectorRenderer.UploadCompletedSignal().Connect(this, &VectorAnimationTask::OnUploadCompleted); } @@ -484,12 +488,38 @@ void VectorAnimationTask::SetLoopingMode(DevelImageVisual::LoopingMode::Type loo void VectorAnimationTask::GetLayerInfo(Property::Map& map) const { - mVectorRenderer.GetLayerInfo(map); + // Fast-out if file is loading, or load failed. + if(mLoadFailed || IsLoadRequested()) + { + return; + } + + if(DALI_UNLIKELY(!mLayerInfoCached)) + { + // Update only 1 time. + mLayerInfoCached = true; + mVectorRenderer.GetLayerInfo(mCachedLayerInfo); + } + + map = mCachedLayerInfo; } void VectorAnimationTask::GetMarkerInfo(Property::Map& map) const { - mVectorRenderer.GetMarkerInfo(map); + // Fast-out if file is loading, or load failed. + if(mLoadFailed || IsLoadRequested()) + { + return; + } + + if(DALI_UNLIKELY(!mMarkerInfoCached)) + { + // Update only 1 time. + mMarkerInfoCached = true; + mVectorRenderer.GetMarkerInfo(mCachedMarkerInfo); + } + + map = mCachedMarkerInfo; } VectorAnimationTask::ResourceReadySignalType& VectorAnimationTask::ResourceReadySignal() diff --git a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h index 367fc8a..dcd66fc 100644 --- a/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h +++ b/dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h @@ -373,6 +373,8 @@ private: ResourceReadySignalType mResourceReadySignal; std::unique_ptr mAnimationFinishedCallback{}; std::unique_ptr mLoadCompletedCallback{}; + mutable Property::Map mCachedLayerInfo; + mutable Property::Map mCachedMarkerInfo; PlayState mPlayState; DevelImageVisual::StopBehavior::Type mStopBehavior; DevelImageVisual::LoopingMode::Type mLoopingMode; @@ -389,15 +391,17 @@ private: uint32_t mAnimationDataIndex; int32_t mLoopCount; int32_t mCurrentLoop; - bool mForward; - bool mUpdateFrameNumber; - bool mNeedAnimationFinishedTrigger; - bool mAnimationDataUpdated; - bool mDestroyTask; - bool mLoadRequest; - bool mLoadFailed; - bool mRasterized; - bool mKeepAnimation; + bool mForward : 1; + bool mUpdateFrameNumber : 1; + bool mNeedAnimationFinishedTrigger : 1; + bool mAnimationDataUpdated : 1; + bool mDestroyTask : 1; + bool mLoadRequest : 1; + bool mLoadFailed : 1; + bool mRasterized : 1; + bool mKeepAnimation : 1; + mutable bool mLayerInfoCached : 1; + mutable bool mMarkerInfoCached : 1; }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/image/image-visual.cpp b/dali-toolkit/internal/visuals/image/image-visual.cpp index 02df28f..ff8d961 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.cpp +++ b/dali-toolkit/internal/visuals/image/image-visual.cpp @@ -680,9 +680,9 @@ void ImageVisual::LoadTexture(bool& atlasing, Vector4& atlasRect, TextureSet& te else { DALI_ASSERT_ALWAYS(mFastTrackLoadingTask->mTextures.size() >= 3u); - textureSet.SetTexture(0u, mFastTrackLoadingTask->mTextures[0]); - textureSet.SetTexture(1u, mFastTrackLoadingTask->mTextures[1]); textureSet.SetTexture(2u, mFastTrackLoadingTask->mTextures[2]); + textureSet.SetTexture(1u, mFastTrackLoadingTask->mTextures[1]); + textureSet.SetTexture(0u, mFastTrackLoadingTask->mTextures[0]); // We cannot determine what kind of shader will be used. // Just use unified shader, and then change shader after load completed. diff --git a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp index e469297..b42bd3b 100644 --- a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp +++ b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp @@ -505,8 +505,8 @@ void NPatchVisual::ApplyTextureAndUniforms() // If we call textureSet.SetTexture(1, texture) directly, the cached TextureSet also be changed. // We should make pass utc-Dali-VisualFactory.cpp UtcDaliNPatchVisualAuxiliaryImage02(). TextureSet tempTextureSet = TextureSet::New(); - tempTextureSet.SetTexture(0, textureSet.GetTexture(0)); tempTextureSet.SetTexture(1, mAuxiliaryTextureSet.GetTexture(0)); + tempTextureSet.SetTexture(0, textureSet.GetTexture(0)); textureSet = tempTextureSet; mImpl->mRenderer.RegisterProperty(DevelImageVisual::Property::AUXILIARY_IMAGE_ALPHA, diff --git a/dali-toolkit/public-api/dali-toolkit-version.cpp b/dali-toolkit/public-api/dali-toolkit-version.cpp index 30096cb..f8a452e 100644 --- a/dali-toolkit/public-api/dali-toolkit-version.cpp +++ b/dali-toolkit/public-api/dali-toolkit-version.cpp @@ -29,7 +29,7 @@ namespace Toolkit { const unsigned int TOOLKIT_MAJOR_VERSION = 2; const unsigned int TOOLKIT_MINOR_VERSION = 2; -const unsigned int TOOLKIT_MICRO_VERSION = 51; +const unsigned int TOOLKIT_MICRO_VERSION = 52; const char* const TOOLKIT_BUILD_DATE = __DATE__ " " __TIME__; #ifdef DEBUG_ENABLED diff --git a/dali-toolkit/public-api/particle-system/particle-types.h b/dali-toolkit/public-api/particle-system/particle-types.h index 41622a6..506241b 100644 --- a/dali-toolkit/public-api/particle-system/particle-types.h +++ b/dali-toolkit/public-api/particle-system/particle-types.h @@ -27,7 +27,7 @@ namespace Dali::Toolkit::ParticleSystem { using ParticleStreamTypeFlagBit = uint32_t; -namespace ParticleStream DALI_TOOLKIT_API +namespace ParticleStream { constexpr ParticleStreamTypeFlagBit POSITION_STREAM_BIT = 1 << 0; ///< 3D Position stream constexpr ParticleStreamTypeFlagBit ROTATION_STREAM_BIT = 1 << 1; ///< 3D Rotation stream diff --git a/packaging/dali-toolkit.spec b/packaging/dali-toolkit.spec index ea0f852..591be97 100644 --- a/packaging/dali-toolkit.spec +++ b/packaging/dali-toolkit.spec @@ -1,6 +1,6 @@ Name: dali2-toolkit Summary: Dali 3D engine Toolkit -Version: 2.2.51 +Version: 2.2.52 Release: 1 Group: System/Libraries License: Apache-2.0 and BSD-3-Clause and MIT