[Tizen](Vector) Finalize VectorThread if application terminated + Wait all lottie... 48/316348/2 accepted/tizen/8.0/unified/20240823.183219
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 13 Aug 2024 04:35:51 +0000 (13:35 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 22 Aug 2024 09:56:35 +0000 (18:56 +0900)
Let we don't do any additional progress after application terminated.

Also, let we keep the VectorAnimationThread lifetime until all
working VectorAnimationTask are completed.

Since that task use VectorAnimationThread as reference, we cannot release
the memory of VectorAnimationManager memory. So just call finalize API.

Change-Id: I6f33c3f5863d8ad3ad9a08d45b134501582d87be
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-manager.h
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-thread.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-thread.h
dali-toolkit/internal/visuals/visual-factory-cache.cpp
dali-toolkit/internal/visuals/visual-factory-cache.h
dali-toolkit/internal/visuals/visual-factory-impl.cpp
dali-toolkit/internal/visuals/visual-factory-impl.h

index 5cac24b611eaa11cb97e2ceb7a296cc1f6835796..d1267af9a27497b0b1149a750c064ed42aa05312 100644 (file)
@@ -2644,4 +2644,58 @@ int UtcDaliAnimatedVectorImageNativeTextureChangeShader(void)
   Test::VectorAnimationRenderer::UseNativeImageTexture(false);
 
   END_TEST;
-}
\ No newline at end of file
+}
+
+int UtcDaliAnimatedVectorImageVisualDestroyApplicationWhenFrameDropped(void)
+{
+  try
+  {
+    {
+      ToolkitTestApplication application;
+      tet_infoline("UtcDaliAnimatedVectorImageVisualDestroyApplicationWhenFrameDropped");
+
+      Property::Map propertyMap;
+      propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
+        .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME_FRAME_DROP)
+        .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<DummyControlImpl&>(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);
+
+      application.SendNotification();
+      application.Render();
+
+      // Trigger count is 2 - load, render the first frame
+      DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
+      Property::Map attributes;
+      DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
+
+      // Make delay to drop frames
+      Test::VectorAnimationRenderer::DelayRendering(500); // block Rasterize thread near 500 ms
+
+      // Request 1 frame rendering.
+      application.SendNotification();
+      application.Render();
+
+      // Destroy applicatoin immediately.
+    }
+
+    tet_result(TET_PASS);
+  }
+  catch(...)
+  {
+    tet_result(TET_FAIL);
+  }
+
+  END_TEST;
+}
index fda5168c0ef031355ba0bebc6b271dc11b4a64a8..2d00244bbaf03272b46abcb6e9c19dc39be68975 100644 (file)
@@ -46,18 +46,14 @@ VectorAnimationManager::VectorAnimationManager()
 : mEventCallbacks(),
   mLifecycleObservers(),
   mVectorAnimationThread(nullptr),
-  mProcessorRegistered(false)
+  mProcessorRegistered(false),
+  mDestroyed(false)
 {
 }
 
 VectorAnimationManager::~VectorAnimationManager()
 {
-  mEventCallbacks.clear();
-
-  if(mProcessorRegistered && Adaptor::IsAvailable())
-  {
-    Adaptor::Get().UnregisterProcessor(*this, true);
-  }
+  Finalize();
 
   for(auto observer : mLifecycleObservers)
   {
@@ -92,12 +88,19 @@ VectorAnimationThread& VectorAnimationManager::GetVectorAnimationThread()
 
 void VectorAnimationManager::RegisterEventCallback(CallbackBase* callback)
 {
-  mEventCallbacks.emplace_back(std::unique_ptr<Dali::CallbackBase>(callback));
+  if(DALI_LIKELY(!mDestroyed))
+  {
+    mEventCallbacks.emplace_back(std::unique_ptr<Dali::CallbackBase>(callback));
 
-  if(!mProcessorRegistered)
+    if(!mProcessorRegistered && DALI_LIKELY(Adaptor::IsAvailable()))
+    {
+      Adaptor::Get().RegisterProcessor(*this, true); // Use post processor to trigger after layoutting
+      mProcessorRegistered = true;
+    }
+  }
+  else
   {
-    Adaptor::Get().RegisterProcessor(*this, true); // Use post processor to trigger after layoutting
-    mProcessorRegistered = true;
+    delete callback; // No longer needed.
   }
 }
 
@@ -123,37 +126,62 @@ void VectorAnimationManager::UnregisterEventCallback(CallbackBase* callback)
   }
 }
 
-void VectorAnimationManager::Process(bool postProcessor)
+void VectorAnimationManager::Finalize()
 {
-#ifdef TRACE_ENABLED
-  if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+  if(DALI_LIKELY(!mDestroyed))
   {
-    if(mEventCallbacks.size() > 0u)
+    DALI_LOG_DEBUG_INFO("Finalizing Vector Animation Manager.\n");
+    mDestroyed = true;
+
+    mEventCallbacks.clear();
+
+    if(mProcessorRegistered && Adaptor::IsAvailable())
+    {
+      Adaptor::Get().UnregisterProcessor(*this, true);
+      mProcessorRegistered = false;
+    }
+
+    if(mVectorAnimationThread)
     {
-      std::ostringstream oss;
-      oss << "[" << mEventCallbacks.size() << "]";
-      DALI_TRACE_BEGIN_WITH_MESSAGE(gTraceFilter, "DALI_VECTOR_ANIMATION_MANAGER_PROCESS", oss.str().c_str());
+      mVectorAnimationThread->Finalize();
     }
   }
-#endif
+}
 
-  for(auto&& iter : mEventCallbacks)
+void VectorAnimationManager::Process(bool postProcessor)
+{
+  if(DALI_LIKELY(!mDestroyed))
   {
-    CallbackBase::Execute(*iter);
-  }
+#ifdef TRACE_ENABLED
+    if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+    {
+      if(mEventCallbacks.size() > 0u)
+      {
+        std::ostringstream oss;
+        oss << "[" << mEventCallbacks.size() << "]";
+        DALI_TRACE_BEGIN_WITH_MESSAGE(gTraceFilter, "DALI_VECTOR_ANIMATION_MANAGER_PROCESS", oss.str().c_str());
+      }
+    }
+#endif
+
+    for(auto&& iter : mEventCallbacks)
+    {
+      CallbackBase::Execute(*iter);
+    }
 
 #ifdef TRACE_ENABLED
-  if(gTraceFilter && gTraceFilter->IsTraceEnabled())
-  {
-    if(mEventCallbacks.size() > 0u)
+    if(gTraceFilter && gTraceFilter->IsTraceEnabled())
     {
-      std::ostringstream oss;
-      oss << "[" << mEventCallbacks.size() << "]";
-      DALI_TRACE_END_WITH_MESSAGE(gTraceFilter, "DALI_VECTOR_ANIMATION_MANAGER_PROCESS", oss.str().c_str());
+      if(mEventCallbacks.size() > 0u)
+      {
+        std::ostringstream oss;
+        oss << "[" << mEventCallbacks.size() << "]";
+        DALI_TRACE_END_WITH_MESSAGE(gTraceFilter, "DALI_VECTOR_ANIMATION_MANAGER_PROCESS", oss.str().c_str());
+      }
     }
-  }
 #endif
-  mEventCallbacks.clear();
+    mEventCallbacks.clear();
+  }
 
   Adaptor::Get().UnregisterProcessor(*this, true);
   mProcessorRegistered = false;
index c3cba215d64b5e08bcd6a58ffd3ad2f90ecdff31..22aaa95969c9c33048f227a84699e687f1ab943a 100644 (file)
@@ -88,6 +88,11 @@ public:
    */
   void UnregisterEventCallback(CallbackBase* callback);
 
+  /**
+   * @brief Finalize the manager. This will stop the animation thread and clear all resources.
+   */
+  void Finalize();
+
 protected: // Implementation of Processor
   /**
    * @copydoc Dali::Integration::Processor::Process()
@@ -113,7 +118,8 @@ private:
   std::vector<std::unique_ptr<CallbackBase>> mEventCallbacks;
   std::vector<LifecycleObserver*>            mLifecycleObservers;
   std::unique_ptr<VectorAnimationThread>     mVectorAnimationThread;
-  bool                                       mProcessorRegistered;
+  bool                                       mProcessorRegistered : 1;
+  bool                                       mDestroyed : 1;
 };
 
 } // namespace Internal
index ac448c022638886aba975689b0f162254d5d649a..d41cec7b1aa62104c2f8ec1cf3b74e13f2303430 100644 (file)
@@ -739,7 +739,6 @@ bool VectorAnimationTask::Rasterize()
   // Forcely trigger render once if need.
   if(mNotifyAfterRasterization || mNeedForceRenderOnceTrigger)
   {
-    Mutex::ScopedLock lock(mMutex);
     mVectorAnimationThread.RequestForceRenderOnce();
     mNeedForceRenderOnceTrigger = false;
   }
index e4c8ebd4b45fac9ba5e837015fe8d7c9e8f00164..82c759c9b40c1154d4aab8a4c5fb25aec2a64e30 100644 (file)
@@ -23,6 +23,7 @@
 #include <dali/devel-api/adaptor-framework/thread-settings.h>
 #include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
 #include <thread>
 
 namespace Dali
@@ -67,15 +68,7 @@ VectorAnimationThread::~VectorAnimationThread()
   // Stop the thread
   {
     ConditionalWait::ScopedLock lock(mConditionalWait);
-    // Wait until some event thread trigger relative job finished.
-    {
-      Mutex::ScopedLock eventTriggerLock(mEventTriggerMutex);
-      Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
-      Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
-      DALI_LOG_DEBUG_INFO("Mark VectorAnimationThread destroyed\n");
-      mDestroyThread = true;
-    }
-    mNeedToSleep = false;
+    Finalize();
     mConditionalWait.Notify(lock);
   }
 
@@ -90,6 +83,41 @@ VectorAnimationThread::~VectorAnimationThread()
   DALI_LOG_DEBUG_INFO("VectorAnimationThread Join request\n");
 
   Join();
+
+  // We need to wait all working tasks are completed before destructing this thread.
+  while(mWorkingTasks.size() > 0)
+  {
+    DALI_LOG_DEBUG_INFO("Still waiting WorkingTasks [%zu]\n", mWorkingTasks.size());
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+
+    // ConditionalWait notifyed when task complete.
+    // If task complete, then remove working tasks list and then wait again, until all working tasks are completed.
+    decltype(mCompletedTasksQueue) completedTasksQueue;
+    {
+      Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+      completedTasksQueue.swap(mCompletedTasksQueue);
+    }
+    if(completedTasksQueue.empty())
+    {
+      mConditionalWait.Wait(lock);
+      // Task completed may have been added to the queue while we were waiting.
+      {
+        Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+        completedTasksQueue.swap(mCompletedTasksQueue);
+      }
+    }
+
+    DALI_LOG_DEBUG_INFO("Completed task queue [%zu]\n", completedTasksQueue.size());
+
+    for(auto& taskPair : completedTasksQueue)
+    {
+      auto workingIter = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), taskPair.first);
+      if(workingIter != mWorkingTasks.end())
+      {
+        mWorkingTasks.erase(workingIter);
+      }
+    }
+  }
 }
 
 /// Event thread called
@@ -113,14 +141,13 @@ void VectorAnimationThread::OnTaskCompleted(VectorAnimationTaskPtr task, bool su
 
   Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
 
-  if(DALI_LIKELY(!mDestroyThread))
-  {
-    mCompletedTasksQueue.emplace_back(task, success && keepAnimation);
+  // DevNote : We need to add task queue, and notify even if mDestroyThread is true.
+  // Since we should make ensure that all working tasks are completed before destroying the thread.
+  mCompletedTasksQueue.emplace_back(task, success && keepAnimation);
 
-    // wake up the animation thread.
-    // Note that we should not make mNeedToSleep as false now.
-    mConditionalWait.Notify(lock);
-  }
+  // wake up the animation thread.
+  // Note that we should not make mNeedToSleep as false now.
+  mConditionalWait.Notify(lock);
 }
 
 /// VectorAnimationThread::SleepThread called, Mutex SleepThread::mAwakeCallbackMutex is locked
@@ -179,6 +206,21 @@ void VectorAnimationThread::RequestForceRenderOnce()
   }
 }
 
+/// Event thread called
+void VectorAnimationThread::Finalize()
+{
+  Mutex::ScopedLock eventTriggerLock(mEventTriggerMutex);
+  Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+  Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
+  // Wait until some event thread trigger, and tasks relative job finished.
+  if(DALI_LIKELY(!mDestroyThread))
+  {
+    DALI_LOG_DEBUG_INFO("Mark VectorAnimationThread destroyed\n");
+    mDestroyThread = true;
+  }
+  mNeedToSleep = false;
+}
+
 /// VectorAnimationThread called
 void VectorAnimationThread::Run()
 {
@@ -186,7 +228,7 @@ void VectorAnimationThread::Run()
   mLogFactory.InstallLogFunction();
   mTraceFactory.InstallTraceFunction();
 
-  while(!mDestroyThread)
+  while(DALI_LIKELY(!mDestroyThread))
   {
     Rasterize();
   }
@@ -237,16 +279,18 @@ bool VectorAnimationThread::MoveTasksToAnimation(VectorAnimationTaskPtr task, bo
 /// VectorAnimationThread called
 void VectorAnimationThread::MoveTasksToCompleted(VectorAnimationTaskPtr task, bool keepAnimation)
 {
-  if(DALI_LIKELY(!mDestroyThread))
-  {
-    bool needRasterize = false;
+  // DevNote : We need to consume task queue, and notify even if mDestroyThread is true.
+  // Since we should make ensure that all working tasks are completed before destroying the thread.
+  bool needRasterize = false;
 
-    auto workingTask = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), task);
-    if(workingTask != mWorkingTasks.end())
-    {
-      mWorkingTasks.erase(workingTask);
-    }
+  auto workingIter = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), task);
+  if(workingIter != mWorkingTasks.end())
+  {
+    mWorkingTasks.erase(workingIter);
+  }
 
+  if(DALI_LIKELY(!mDestroyThread))
+  {
     // Check pending task
     {
       Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
index 22d82ce3a23d6400116290060a1771cdf00a42da..e3e621838ad0418a3d481eacc82f6d56a5bd8254 100644 (file)
@@ -95,6 +95,11 @@ public:
    */
   void RequestForceRenderOnce();
 
+  /**
+   * @brief Finalize the thread.
+   */
+  void Finalize();
+
 protected:
   /**
    * @brief The entry function of the animation thread.
index e8b5dbfd1bc86dafe0a77c79c8973ea0fbcae5b7..1be616d73f760e4d2b2ff5607a9590452ba68a5a 100644 (file)
@@ -156,6 +156,14 @@ VectorAnimationManager& VisualFactoryCache::GetVectorAnimationManager()
   return *mVectorAnimationManager;
 }
 
+void VisualFactoryCache::FinalizeVectorAnimationManager()
+{
+  if(mVectorAnimationManager)
+  {
+    mVectorAnimationManager->Finalize();
+  }
+}
+
 Geometry VisualFactoryCache::CreateGridGeometry(Uint16Pair gridSize)
 {
   uint16_t gridWidth  = gridSize.GetWidth();
index 1b3f098a6fd1f235f8725c03be226b29efc9c41f..9f2db45756c4d8907aefdbb0d9a47bae8ff4dc00 100644 (file)
@@ -265,6 +265,12 @@ public:
    */
   VectorAnimationManager& GetVectorAnimationManager();
 
+  /**
+   * @brief Finalize vector animation manager.
+   * It will be called when application is terminated.
+   */
+  void FinalizeVectorAnimationManager();
+
 protected:
   /**
    * Undefined copy constructor.
index 91584eb4220a198860f9ced31fc4b7a9f856ca26..dd421bbc612ba3e16852bac84b8696e5e4d0774c 100644 (file)
@@ -18,6 +18,7 @@
 #include <dali-toolkit/internal/visuals/visual-factory-impl.h>
 
 // EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/lifecycle-controller.h>
 #include <dali/devel-api/scripting/scripting.h>
 #include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
@@ -103,15 +104,29 @@ VisualFactory::VisualFactory(bool debugEnabled)
   mPreMultiplyOnLoad(true),
   mPrecompiledShaderRequested(false)
 {
+  Dali::LifecycleController lifecycleController = Dali::LifecycleController::Get();
+  if(DALI_LIKELY(lifecycleController))
+  {
+    lifecycleController.TerminateSignal().Connect(this, &VisualFactory::OnApplicationTerminated);
+  }
 }
 
 VisualFactory::~VisualFactory()
 {
-  if(mIdleCallback && Adaptor::IsAvailable())
+  if(Adaptor::IsAvailable())
   {
-    // Removes the callback from the callback manager in case the control is destroyed before the callback is executed.
-    Adaptor::Get().RemoveIdle(mIdleCallback);
-    mIdleCallback = nullptr;
+    Dali::LifecycleController lifecycleController = Dali::LifecycleController::Get();
+    if(DALI_LIKELY(lifecycleController))
+    {
+      lifecycleController.TerminateSignal().Disconnect(this, &VisualFactory::OnApplicationTerminated);
+    }
+
+    if(mIdleCallback)
+    {
+      // Removes the callback from the callback manager in case the control is destroyed before the callback is executed.
+      Adaptor::Get().RemoveIdle(mIdleCallback);
+      mIdleCallback = nullptr;
+    }
   }
 }
 
@@ -531,6 +546,19 @@ void VisualFactory::OnDiscardCallback()
   mDiscardedVisuals.clear();
 }
 
+void VisualFactory::OnApplicationTerminated()
+{
+  if(DALI_UNLIKELY(mIdleCallback))
+  {
+    OnDiscardCallback();
+  }
+
+  if(mFactoryCache)
+  {
+    mFactoryCache->FinalizeVectorAnimationManager();
+  }
+}
+
 void VisualFactory::RegisterDiscardCallback()
 {
   if(!mIdleCallback && Adaptor::IsAvailable())
index 76bacf90f21f1d6838b0c745a604c466d5ddf80b..33330de9948156c2fcc227b2250cbd9b1f11fa7c 100644 (file)
@@ -41,7 +41,7 @@ class TextVisualShaderFactory;
 /**
  * @copydoc Toolkit::VisualFactory
  */
-class VisualFactory : public BaseObject
+class VisualFactory : public BaseObject, public ConnectionTracker
 {
 public:
   /**
@@ -145,6 +145,11 @@ private:
    */
   void RegisterDiscardCallback();
 
+  /**
+   * @brief Callbacks called when application is terminated.
+   */
+  void OnApplicationTerminated();
+
   VisualFactory(const VisualFactory&) = delete;
 
   VisualFactory& operator=(const VisualFactory& rhs) = delete;