[Tizen] Add TasksCompleted signal at AsyncTaskManager
authordongsug.song <dongsug.song@samsung.com>
Mon, 6 Nov 2023 10:09:31 +0000 (19:09 +0900)
committerdongsug.song <dongsug.song@samsung.com>
Mon, 6 Nov 2023 10:09:45 +0000 (19:09 +0900)
This reverts commit e25a5c002505e218250b16ae6c6f843a2e21d48b.

Change-Id: Ieee91d4b33f464d03370c7fd6a28071d268dcfdf

dali/internal/system/common/async-task-manager-impl.cpp
dali/internal/system/common/async-task-manager-impl.h
dali/public-api/adaptor-framework/async-task-manager.cpp
dali/public-api/adaptor-framework/async-task-manager.h

index 9fcf9b4..dabb4ae 100644 (file)
@@ -161,6 +161,335 @@ void AsyncTaskThread::Run()
   }
 }
 
+// AsyncTaskManager::TasksCompletedImpl
+
+struct AsyncTaskManager::TasksCompletedImpl
+{
+  TasksCompletedImpl(AsyncTaskManager& manager)
+  : mManager(manager),
+    mEmitCompletedTaskRegistered(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)});
+
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "GenerateTasksCompletedId id[%u] callback[%p]\n", id, 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)
+  {
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "AppendTaskTrace id[%u] task[%p]\n", id, task.Get());
+
+    // 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;
+    }
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "RemoveTaskTrace task[%p] remove count[%u]\n", task.Get(), count);
+
+    // 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())
+      {
+        DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "RemoveTaskTrace id[%u] task[%p], current refcount[%u]\n", iter->first, task.Get(), (jter->second));
+
+        if(jter->second <= count)
+        {
+          callbackData.mTasks.erase(jter);
+
+          DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "RemoveTaskTrace id[%u] task erased. remained tasks[%zu]", iter->first, callbackData.mTasks.size());
+
+          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);
+
+            DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "id[%u] completed!\n", 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)
+  {
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "CheckTasksCompletedCallbackCompleted[%u]\n", 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);
+
+        DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "id[%u] completed!\n", 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)
+  {
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "RemoveTasksCompleted[%u]\n", 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.
+   */
+  void EmitCompletedTasks()
+  {
+    ExecuteCallbackContainer executeCallbackList;
+    {
+      // Lock while removing excute callback list to the queue
+      Mutex::ScopedLock lock(mExcuteCallbacksMutex);
+
+      mEmitCompletedTaskRegistered = false;
+
+      // Copy callback lists, for let we execute callbacks out of mutex
+      executeCallbackList = std::move(mExcuteCallbackList);
+      mExcuteCallbackList.clear();
+    }
+
+    if(!executeCallbackList.empty())
+    {
+      DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Excute callback count[%zu]\n", executeCallbackList.size());
+      // Execute all callbacks
+      for(auto&& callbackPair : executeCallbackList)
+      {
+        auto& callback = callbackPair.first;
+        auto  id       = callbackPair.second;
+
+        DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Excute taskS completed callback[%p] for id[%u]\n", callback.get(), id);
+
+        Dali::CallbackBase::Execute(*callback, id);
+      }
+
+      DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Excute callback end\n");
+    }
+  }
+
+  /**
+   * @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<CallbackBase> callback, Dali::AsyncTaskManager::TasksCompletedId id)
+  {
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "TasksCompleted[%u] need to be execute with callback[%p]\n", id, callback.get());
+
+    // Lock while adding excute callback list to the queue
+    Mutex::ScopedLock lock(mExcuteCallbacksMutex);
+
+    mExcuteCallbackList.emplace_back(std::move(callback), id);
+
+    if(!mEmitCompletedTaskRegistered)
+    {
+      mEmitCompletedTaskRegistered = true;
+
+      DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Register processor\n");
+      mManager.RegisterProcessor();
+    }
+  }
+
+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<CallbackBase>                  mCallback;
+    std::unordered_map<const AsyncTask*, uint32_t> mTasks;
+  };
+
+private:
+  AsyncTaskManager& mManager; ///< Owner of this CacheImpl.
+
+  Dali::AsyncTaskManager::TasksCompletedId mTasksCompletedCount{0u};
+
+  using TasksCompletedContainer = std::unordered_map<Dali::AsyncTaskManager::TasksCompletedId, CallbackData>;
+  TasksCompletedContainer mTasksCompletedCallbackList;
+
+  using ExecuteCallbackContainer = std::vector<std::pair<std::unique_ptr<CallbackBase>, 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 mEmitCompletedTaskRegistered : 1;
+};
+
 // AsyncTaskManager::CacheImpl
 
 struct AsyncTaskManager::CacheImpl
@@ -266,6 +595,7 @@ AsyncTaskManager::AsyncTaskManager()
 : mTasks(GetNumberOfThreads(NUMBER_OF_ASYNC_THREADS_ENV, DEFAULT_NUMBER_OF_ASYNC_THREADS), [&]() { return TaskHelper(*this); }),
   mAvaliableLowPriorityTaskCounts(GetNumberOfLowPriorityThreads(NUMBER_OF_LOW_PRIORITY_THREADS_ENV, DEFAULT_NUMBER_OF_LOW_PRIORITY_THREADS, mTasks.GetElementCount())),
   mWaitingHighProirityTaskCounts(0u),
+  mTasksCompletedImpl(new TasksCompletedImpl(*this)),
   mCacheImpl(new CacheImpl(*this)),
   mTrigger(new EventThreadCallback(MakeCallback(this, &AsyncTaskManager::TasksCompleted))),
   mProcessorRegistered(false)
@@ -337,11 +667,7 @@ void AsyncTaskManager::AddTask(AsyncTaskPtr task)
   }
 
   // Register Process (Since mTrigger execute too late timing if event thread running a lots of events.)
-  if(!mProcessorRegistered && Dali::Adaptor::IsAvailable())
-  {
-    Dali::Adaptor::Get().RegisterProcessor(*this);
-    mProcessorRegistered = true;
-  }
+  RegisterProcessor();
 
   return;
 }
@@ -356,6 +682,8 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
     // If there is some non-empty queue exist, we don't need to unregister processor.
     bool needCheckUnregisterProcessor = true;
 
+    uint32_t removedCount = 0u;
+
     {
       // Lock while remove task from the queue
       Mutex::ScopedLock lock(mWaitingTasksMutex);
@@ -372,6 +700,7 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
             --mWaitingHighProirityTaskCounts;
           }
           mWaitingTasks.erase(iterator);
+          ++removedCount;
         }
         CacheImpl::EraseAllTaskCache(mCacheImpl->mWaitingTasksCache, task);
       }
@@ -395,6 +724,7 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
           // We cannot erase container. Just mark as canceled.
           // Note : mAvaliableLowPriorityTaskCounts will be increased after process finished.
           (*iterator).second = RunningTaskState::CANCELED;
+          ++removedCount;
         }
       }
 
@@ -415,6 +745,7 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
         {
           DALI_ASSERT_DEBUG(iterator->first == task);
           mCompletedTasks.erase(iterator);
+          ++removedCount;
         }
         CacheImpl::EraseAllTaskCache(mCacheImpl->mCompletedTasksCache, task);
       }
@@ -425,6 +756,12 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
       }
     }
 
+    // Remove TasksCompleted callback trace
+    if(removedCount > 0u)
+    {
+      mTasksCompletedImpl->RemoveTaskTrace(task, removedCount);
+    }
+
     // UnregisterProcessor required to lock mutex. Call this API only if required.
     if(needCheckUnregisterProcessor)
     {
@@ -433,6 +770,82 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
   }
 }
 
+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.
+
+  DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "SetCompletedCallback id : %u, mask : %d\n", tasksCompletedId, static_cast<int32_t>(mask));
+
+  // 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)
+  {
+    DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "CompletedCallback id[%u] executed now due to no task exist\n", tasksCompletedId);
+
+    mTasksCompletedImpl->CheckTasksCompletedCallbackCompleted(tasksCompletedId);
+  }
+  return tasksCompletedId;
+}
+
+bool AsyncTaskManager::RemoveCompletedCallback(Dali::AsyncTaskManager::TasksCompletedId tasksCompletedId)
+{
+  return mTasksCompletedImpl->RemoveTasksCompleted(tasksCompletedId);
+}
+
 AsyncTaskPtr AsyncTaskManager::PopNextCompletedTask()
 {
   std::vector<AsyncTaskPtr> ignoredTaskList; ///< To keep asyncTask reference so we can ensure that destructor called out of mutex.
@@ -469,6 +882,15 @@ AsyncTaskPtr AsyncTaskManager::PopNextCompletedTask()
   return nextCompletedTask;
 }
 
+void AsyncTaskManager::RegisterProcessor()
+{
+  if(!mProcessorRegistered && Dali::Adaptor::IsAvailable())
+  {
+    Dali::Adaptor::Get().RegisterProcessor(*this);
+    mProcessorRegistered = true;
+  }
+}
+
 void AsyncTaskManager::UnregisterProcessor()
 {
   if(mProcessorRegistered && Dali::Adaptor::IsAvailable())
@@ -502,10 +924,15 @@ void AsyncTaskManager::TasksCompleted()
   {
     DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Execute callback [%p]\n", task.Get());
     CallbackBase::Execute(*(task->GetCompletedCallback()), task);
+
+    // Remove TasksCompleted callback trace
+    mTasksCompletedImpl->RemoveTaskTrace(task);
   }
 
   UnregisterProcessor();
   DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "TasksCompleted end\n");
+
+  mTasksCompletedImpl->EmitCompletedTasks();
 }
 
 void AsyncTaskManager::Process(bool postProcessor)
@@ -627,6 +1054,18 @@ void AsyncTaskManager::CompleteTask(AsyncTaskPtr&& task)
     {
       DALI_LOG_INFO(gAsyncTasksManagerLogFilter, Debug::Verbose, "Execute callback on worker thread [%p]\n", task.Get());
       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
index 6690098..5efd4f4 100644 (file)
@@ -120,6 +120,16 @@ public:
   void RemoveTask(AsyncTaskPtr task);
 
   /**
+   * @copydoc Dali::AsyncTaskManager::SetCompletedCallback()
+   */
+  Dali::AsyncTaskManager::TasksCompletedId SetCompletedCallback(CallbackBase* callback, Dali::AsyncTaskManager::CompletedCallbackTraceMask mask);
+
+  /**
+   * @copydoc Dali::AsyncTaskManager::RemoveCompletedCallback()
+   */
+  bool RemoveCompletedCallback(Dali::AsyncTaskManager::TasksCompletedId tasksCompletedId);
+
+  /**
    * Pop the next task out from the completed queue, called by main thread.
    *
    * @return The next task in the completed queue.
@@ -127,6 +137,11 @@ public:
   AsyncTaskPtr PopNextCompletedTask();
 
   /**
+   * @brief Register processor if we don't registered before.
+   */
+  void RegisterProcessor();
+
+  /**
    * @brief Unregister a previously registered processor
    */
   void UnregisterProcessor();
@@ -248,6 +263,9 @@ private:
   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.
 
+  struct TasksCompletedImpl;
+  std::unique_ptr<TasksCompletedImpl> mTasksCompletedImpl; ///< TaskS completed signal interface for AsyncTaskManager.
+
   struct CacheImpl;
   std::unique_ptr<CacheImpl> mCacheImpl; ///< Cache interface for AsyncTaskManager.
 
index 3d19b7e..b160954 100644 (file)
@@ -34,7 +34,7 @@ CallbackBase* AsyncTask::GetCompletedCallback()
   return mCompletedCallback.get();
 }
 
-AsyncTask::ThreadType AsyncTask::GetCallbackInvocationThread()
+AsyncTask::ThreadType AsyncTask::GetCallbackInvocationThread() const
 {
   return mThreadType;
 }
@@ -63,6 +63,16 @@ void AsyncTaskManager::RemoveTask(AsyncTaskPtr task)
   GetImplementation(*this).RemoveTask(task);
 }
 
+AsyncTaskManager::TasksCompletedId AsyncTaskManager::SetCompletedCallback(CallbackBase* callback, AsyncTaskManager::CompletedCallbackTraceMask mask)
+{
+  return GetImplementation(*this).SetCompletedCallback(callback, mask);
+}
+
+bool AsyncTaskManager::RemoveCompletedCallback(AsyncTaskManager::TasksCompletedId tasksCompletedId)
+{
+  return GetImplementation(*this).RemoveCompletedCallback(tasksCompletedId);
+}
+
 AsyncTaskManager::AsyncTaskManager(Internal::Adaptor::AsyncTaskManager* impl)
 : BaseHandle(impl)
 {
index 10a30fc..087a77a 100644 (file)
@@ -97,7 +97,7 @@ public:
    * @SINCE_2_2.9
    * @return the type of invocation callback.
    */
-  ThreadType GetCallbackInvocationThread();
+  ThreadType GetCallbackInvocationThread() const;
 
   /**
    * Get the priority of this task
@@ -181,6 +181,71 @@ public:
   void RemoveTask(AsyncTaskPtr task);
 
 public:
+  using TasksCompletedId = uint32_t;
+
+  enum CompletedCallbackTraceMask
+  {
+    THREAD_MASK_MAIN   = 1u << 0, ///< Trace only main thread tasks.
+    THREAD_MASK_WORKER = 1u << 1, ///< Trace only worker thread tasks.
+
+    PRIORITY_MASK_HIGH = 1u << 2, ///< Trace only high priority tasks.
+    PRIORITY_MASK_LOW  = 1u << 3, ///< Trace only low priority tasks.
+
+    THREAD_MASK_ALL   = THREAD_MASK_MAIN | THREAD_MASK_WORKER,
+    PRIORITY_MASK_ALL = PRIORITY_MASK_HIGH | PRIORITY_MASK_LOW,
+
+    MASK_ALL = THREAD_MASK_ALL | PRIORITY_MASK_ALL,
+
+    DEFAULT = MASK_ALL,
+  };
+
+  /**
+   * @brief Set the async tasks completed callback.
+   * Inputed callback will be emitted after all tasks what user added are completed.
+   *
+   * Usage example:
+   *
+   *   void OnTasksCompleted(TasksCompletedId id);
+   *   auto id0 = AsyncTaskManager::Get().SetCompletedCallback(MakeCallback(OnTasksCompleted), CompletedCallbackTraceMask::MASK_ALL);
+   *   // OnTasksCompleted(id0); called at next Idler.
+   *
+   *   AsyncTaskManager::Get().AddTask(task1);
+   *   auto id1 = AsyncTaskManager::Get().SetCompletedCallback(MakeCallback(OnTasksCompleted), CompletedCallbackTraceMask::MASK_ALL);
+   *   // OnTasksCompleted(id1); called after task1 completed.
+   *
+   *   AsyncTaskManager::Get().AddTask(task2WhichIsLowPriority);
+   *   AsyncTaskManager::Get().AddTask(task3WhichIsWorkerThread);
+   *   AsyncTaskManager::Get().AddTask(task4);
+   *   auto id2 = AsyncTaskManager::Get().SetCompletedCallback(MakeCallback(OnTasksCompleted), CompletedCallbackTraceMask::THREAD_MASK_MAIN | CompletedCallbackTraceMask::PRIORITY_MASK_HIGH);
+   *   // OnTasksCompleted(id2); called after task1 and task4 completed.
+   *
+   *   AsyncTaskManager::Get().RemoveCompletedCallback(id1);
+   *   // OnTasksCompleted(id1); will not be called.
+   *
+   * @note The ownership of callback will be hold AsyncTaskManager itself.
+   * @note The callback will be emmited at Process() timming.
+   *
+   * @SINCE_2_2.50
+   * @param[in] callback The callback base when all AsyncTasks completed.
+   *                     This callback will be void return, and single input argument ; TasksCompletedId.
+   * @param[in] mask Mask info s.t. what kind of async task we want to detact.
+   *                 For example, if we set this value as MASK_ALL & ~PRIORITY_MASK_LOW, we will ignore low priority tasks.
+   *                 Default is MASK_ALL.
+   * @return The unique id for callback. It can be used when we want to remove callback.
+   */
+  TasksCompletedId SetCompletedCallback(CallbackBase* callback, CompletedCallbackTraceMask mask = CompletedCallbackTraceMask::DEFAULT);
+
+  /**
+   * @brief Remove the async tasks completed callback.
+   * @note It will not execute setted callback.
+   *
+   * @SINCE_2_2.50
+   * @param[in] tasksCompletedId The id for callback that want to remove.
+   * @return True if we success to removed. False if it already removed, or callback already emitted.
+   */
+  bool RemoveCompletedCallback(TasksCompletedId tasksCompletedId);
+
+public:
   /// @cond internal
   /**
    * @brief Allows the creation of a AsyncTaskManager handle from an internal pointer.