Guard NPatchData removal case during signal emit 67/297967/5
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 29 Aug 2023 06:52:47 +0000 (15:52 +0900)
committerEunki Hong <eunkiki.hong@samsung.com>
Mon, 4 Sep 2023 06:00:43 +0000 (06:00 +0000)
It was possible that NPatchData removed during NotifyObserver.
For more safety, let we make NPatchData as shared_ptr, instead
of unique_ptr.

And also, let we don't touch observer list container during
NotifyObservers.

And also, let we remove NPatchInfo at post processing.
It will keep the life of NPatchData during NotifyObserver try to
remove the NPatchInfo.

Change-Id: Ieca33a2231df38d23966f96593506d67333cfcd0
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
dali-toolkit/internal/visuals/npatch-data.cpp
dali-toolkit/internal/visuals/npatch-data.h
dali-toolkit/internal/visuals/npatch-loader.cpp
dali-toolkit/internal/visuals/npatch-loader.h
dali-toolkit/internal/visuals/npatch/npatch-visual.cpp

index ccc8eae..6fb05ab 100644 (file)
@@ -71,6 +71,8 @@ const char* TEST_BROKEN_IMAGE_L       = TEST_RESOURCE_DIR "/broken_l.9.png";
 const char* TEST_BROKEN_IMAGE_01      = TEST_RESOURCE_DIR "/button-up.9.png";
 const char* TEST_BROKEN_IMAGE_02      = TEST_RESOURCE_DIR "/heartsframe.9.png";
 
+const char* TEST_INVALID_NPATCH_FILE_NAME_01 = "invalid1.9.png";
+
 // resolution: 34*34, pixel format: RGBA8888
 static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png";
 // resolution: 600*600, pixel format: RGB888
@@ -3672,6 +3674,45 @@ void OnResourceReadySignal07(Control control)
   }
 }
 
+void OnResourceReadySignal08(Control control)
+{
+  gResourceReadySignalCounter++;
+
+  if(gImageView1)
+  {
+    gImageView1.Unparent();
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Unparent();
+    gImageView2.Reset();
+  }
+}
+
+std::size_t gResourceReadySignal09Emitted = false;
+
+void OnResourceReadySignal09(Control control)
+{
+  gResourceReadySignalCounter++;
+
+  if(gImageView1 && !gResourceReadySignal09Emitted)
+  {
+    gResourceReadySignal09Emitted = true;
+    gImageView1.ResourceReadySignal().Disconnect(&OnResourceReadySignal09);
+
+    // Try to load cached invalid nine patch image. It will request load now.
+    gImageView1.SetImage(TEST_INVALID_NPATCH_FILE_NAME_01);
+    gImageView2.SetImage(TEST_INVALID_NPATCH_FILE_NAME_01);
+
+    // Destroy all visuals immediatly.
+    gImageView1.Unparent();
+    gImageView1.Reset();
+    gImageView2.Unparent();
+    gImageView2.Reset();
+  }
+}
+
 } // namespace
 
 int UtcDaliImageViewSetImageOnResourceReadySignal01(void)
@@ -4208,6 +4249,239 @@ int UtcDaliImageViewSetImageOnResourceReadySignal07(void)
   END_TEST;
 }
 
+int UtcDaliImageViewSetImageOnResourceReadySignal08(void)
+{
+  tet_infoline("Test remove npatch images during resource ready");
+
+  ToolkitTestApplication application;
+
+  gResourceReadySignalCounter = 0;
+
+  Property::Map map;
+  map[Toolkit::ImageVisual::Property::URL] = TEST_BROKEN_IMAGE_M;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+
+  // Case 1 : Remove all images during resource ready.
+  try
+  {
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal08);
+    application.GetScene().Add(gImageView1);
+
+    application.SendNotification();
+    application.Render();
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 1, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+
+  // Clear cache.
+  application.SendNotification();
+  application.Render();
+
+  gResourceReadySignalCounter = 0;
+
+  // Case 2 : Remove all images when we use cached resource.
+  try
+  {
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    application.GetScene().Add(gImageView1);
+
+    application.SendNotification();
+    application.Render();
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 1, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    gImageView2 = ImageView::New();
+    gImageView2.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView2.ResourceReadySignal().Connect(&OnResourceReadySignal08);
+    application.GetScene().Add(gImageView2);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 2, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+  gResourceReadySignalCounter = 0;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+
+  END_TEST;
+}
+
+int UtcDaliImageViewSetImageOnResourceReadySignal09(void)
+{
+  tet_infoline("Test load invalid npatch images during invalid resource ready");
+
+  ToolkitTestApplication application;
+
+  gResourceReadySignalCounter = 0;
+
+  Property::Map map;
+  map[Toolkit::ImageVisual::Property::URL] = TEST_INVALID_NPATCH_FILE_NAME_01;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+  if(gImageView3)
+  {
+    gImageView3.Reset();
+  }
+
+  // Dummy view with npatch image
+  ImageView dummyView = ImageView::New(TEST_BROKEN_IMAGE_M);
+  application.GetScene().Add(dummyView);
+
+  application.SendNotification();
+  application.Render();
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Case 1 : Reload images during resource ready.
+  try
+  {
+    gResourceReadySignal09Emitted = false;
+
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal09);
+    application.GetScene().Add(gImageView1);
+
+    gImageView2 = ImageView::New();
+    application.GetScene().Add(gImageView2);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01 one more times.
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+
+  // Clear cache.
+  application.SendNotification();
+  application.Render();
+
+  gResourceReadySignalCounter = 0;
+
+  // Case 2 : Remove all images when we use cached resource.
+  try
+  {
+    gResourceReadySignal09Emitted = false;
+
+    gImageView3 = ImageView::New();
+    gImageView3.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView3.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    application.GetScene().Add(gImageView3);
+
+    gImageView2 = ImageView::New();
+    application.GetScene().Add(gImageView2);
+
+    // Load gImageView2
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal09);
+    application.GetScene().Add(gImageView1);
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01 one more times.
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+  gResourceReadySignalCounter = 0;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+  if(gImageView3)
+  {
+    gImageView3.Reset();
+  }
+
+  END_TEST;
+}
+
 int UtcDaliImageViewUseSameUrlWithAnimatedImageVisual(void)
 {
   tet_infoline("Test multiple views with same image in animated image visual");
@@ -4247,6 +4521,11 @@ int UtcDaliImageViewNpatchImageCacheTest01(void)
     {
       imageView[index].Unparent();
     }
+
+    // Ensure remove npatch cache if required.
+    application.SendNotification();
+    application.Render();
+
     imageView[index] = ImageView::New(nPatchImageUrl);
     imageView[index].SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
     application.GetScene().Add(imageView[index]);
index dc0a055..62c706e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,8 +39,9 @@ NPatchData::NPatchData()
   mCroppedHeight(0),
   mBorder(0, 0, 0, 0),
   mLoadingState(LoadingState::NOT_STARTED),
+  mRenderingMap{nullptr},
   mPreMultiplyOnLoad(false),
-  mRenderingMap{nullptr}
+  mObserverNotifying(false)
 {
 }
 
@@ -51,6 +52,8 @@ NPatchData::~NPatchData()
   {
     RenderingAddOn::Get().DestroyNPatch(mRenderingMap);
   }
+  mObserverList.Clear();
+  mQueuedObservers.Clear();
 }
 
 void NPatchData::SetId(const NPatchDataId id)
@@ -65,7 +68,16 @@ NPatchData::NPatchDataId NPatchData::GetId() const
 
 void NPatchData::AddObserver(TextureUploadObserver* textureObserver)
 {
-  mObserverList.PushBack(textureObserver);
+  if(mObserverNotifying)
+  {
+    // Do not add it into observer list during observer notifying.
+    mQueuedObservers.PushBack(textureObserver);
+  }
+  else
+  {
+    mObserverList.PushBack(textureObserver);
+  }
+  textureObserver->DestructionSignal().Connect(this, &NPatchData::ObserverDestroyed);
 }
 
 void NPatchData::RemoveObserver(TextureUploadObserver* textureObserver)
@@ -74,6 +86,7 @@ void NPatchData::RemoveObserver(TextureUploadObserver* textureObserver)
   {
     if(textureObserver == mObserverList[index])
     {
+      textureObserver->DestructionSignal().Disconnect(this, &NPatchData::ObserverDestroyed);
       mObserverList.Erase(mObserverList.begin() + index);
       break;
     }
@@ -259,13 +272,57 @@ void NPatchData::LoadComplete(bool loadSuccess, TextureInformation textureInform
     }
   }
 
-  for(uint32_t index = 0; index < mObserverList.Count(); ++index)
+  mObserverNotifying = true;
+
+  // Reverse observer list that we can pop_back the observer.
+  std::reverse(mObserverList.Begin(), mObserverList.End());
+
+  while(mObserverList.Count() > 0u)
   {
-    TextureUploadObserver* observer = mObserverList[index];
+    TextureUploadObserver* observer = *(mObserverList.End() - 1u);
+    mObserverList.Erase(mObserverList.End() - 1u);
+
+    observer->DestructionSignal().Disconnect(this, &NPatchData::ObserverDestroyed);
+
     NotifyObserver(observer, loadSuccess);
   }
 
-  mObserverList.Clear();
+  mObserverNotifying = false;
+
+  // Swap observer list what we queued during notify observer.
+  // If mQueuedObserver is not empty, it mean mLoadingState was LOAD_FAILED, and we try to re-load for this data.
+  // (If mLoadingState was LOAD_COMPLETE, NotifyObserver will be called directly. @todo : Should we fix this logic, matched with texture manager?)
+  // So LoadComplete will be called.
+  mObserverList.Swap(mQueuedObservers);
+}
+
+void NPatchData::ObserverDestroyed(TextureUploadObserver* observer)
+{
+  for(auto iter = mObserverList.Begin(); iter != mObserverList.End();)
+  {
+    if(observer == (*iter))
+    {
+      iter = mObserverList.Erase(iter);
+    }
+    else
+    {
+      ++iter;
+    }
+  }
+  if(mObserverNotifying)
+  {
+    for(auto iter = mQueuedObservers.Begin(); iter != mQueuedObservers.End();)
+    {
+      if(observer == (*iter))
+      {
+        iter = mQueuedObservers.Erase(iter);
+      }
+      else
+      {
+        ++iter;
+      }
+    }
+  }
 }
 
 } // namespace Internal
index cc520d8..3df4b29 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_NPATCH_DATA_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -33,11 +33,11 @@ namespace Toolkit
 {
 namespace Internal
 {
-class NPatchData : public Dali::Toolkit::TextureUploadObserver
+class NPatchData : public ConnectionTracker, public Dali::Toolkit::TextureUploadObserver
 {
 public:
-  typedef int32_t  NPatchDataId;                ///< The NPatchDataId type. This is used as a handle to refer to a particular Npatch Data.
-  static const int INVALID_NPATCH_DATA_ID = -1; ///< Used to represent a null TextureId or error
+  typedef int32_t           NPatchDataId;                ///< The NPatchDataId type. This is used as a handle to refer to a particular Npatch Data.
+  static const NPatchDataId INVALID_NPATCH_DATA_ID = -1; ///< Used to represent a null TextureId or error
 
   /**
    * @brief Loading State of the NPatch image.
@@ -269,22 +269,32 @@ private:
    */
   void LoadComplete(bool loadSuccess, TextureInformation textureInformation) override;
 
+  /**
+   * This is called by the TextureUploadObserver when an observer is destroyed.
+   * We use the callback to know when to remove an observer from our notify list.
+   * @param[in] observer The observer that generated the callback
+   */
+  void ObserverDestroyed(TextureUploadObserver* observer);
+
 private:
   using ObserverListType = Dali::Vector<TextureUploadObserver*>;
 
   NPatchDataId                 mId;
-  ObserverListType             mObserverList;      ///< Container used to store all observer clients of this Texture
-  VisualUrl                    mUrl;               ///< Url of the N-Patch
-  TextureSet                   mTextureSet;        ///< Texture containing the cropped image
-  NPatchUtility::StretchRanges mStretchPixelsX;    ///< X stretch pixels
-  NPatchUtility::StretchRanges mStretchPixelsY;    ///< Y stretch pixels
-  std::size_t                  mHash;              ///< Hash code for the Url
-  uint32_t                     mCroppedWidth;      ///< Width of the cropped middle part of N-patch
-  uint32_t                     mCroppedHeight;     ///< Height of the cropped middle part of N-patch
-  Rect<int>                    mBorder;            ///< The size of the border
-  LoadingState                 mLoadingState;      ///< True if the data loading is completed
-  bool                         mPreMultiplyOnLoad; ///< Whether to multiply alpha into color channels on load
-  void*                        mRenderingMap;      ///< NPatch rendering data
+  ObserverListType             mObserverList;    ///< Container used to store all observer clients of this Texture
+  ObserverListType             mQueuedObservers; ///< Container observers when user try to add during notify observers
+  VisualUrl                    mUrl;             ///< Url of the N-Patch
+  TextureSet                   mTextureSet;      ///< Texture containing the cropped image
+  NPatchUtility::StretchRanges mStretchPixelsX;  ///< X stretch pixels
+  NPatchUtility::StretchRanges mStretchPixelsY;  ///< Y stretch pixels
+  std::size_t                  mHash;            ///< Hash code for the Url
+  uint32_t                     mCroppedWidth;    ///< Width of the cropped middle part of N-patch
+  uint32_t                     mCroppedHeight;   ///< Height of the cropped middle part of N-patch
+  Rect<int>                    mBorder;          ///< The size of the border
+  LoadingState                 mLoadingState;    ///< True if the data loading is completed
+  void*                        mRenderingMap;    ///< NPatch rendering data
+
+  bool mPreMultiplyOnLoad : 1; ///< Whether to multiply alpha into color channels on load
+  bool mObserverNotifying : 1; ///< Whether this NPatchData notifying observers or not.
 };
 
 } // namespace Internal
index 5b2b713..3e18482 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
 
 // EXTERNAL HEADERS
 #include <dali/devel-api/common/hash.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
 
 namespace Dali
@@ -39,24 +40,35 @@ constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to
 } // Anonymous namespace
 
 NPatchLoader::NPatchLoader()
-: mCurrentNPatchDataId(0)
+: mCurrentNPatchDataId(0),
+  mRemoveProcessorRegistered(false)
 {
 }
 
 NPatchLoader::~NPatchLoader()
 {
+  if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
+  }
 }
 
 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
 {
+  // Skip invalid id generation.
+  if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
+  {
+    mCurrentNPatchDataId = 0;
+  }
   return mCurrentNPatchDataId++;
 }
 
-std::size_t NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
+NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
 {
-  NPatchData* data = GetNPatchData(url, border, preMultiplyOnLoad);
+  std::shared_ptr<NPatchData> data = GetNPatchData(url, border, preMultiplyOnLoad);
 
-  DALI_ASSERT_ALWAYS(data && "NPatchData creation failed!");
+  DALI_ASSERT_ALWAYS(data.get() && "NPatchData creation failed!");
 
   if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
   {
@@ -85,7 +97,7 @@ std::size_t NPatchLoader::Load(TextureManager& textureManager, TextureUploadObse
     auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
                                                   : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
 
-    Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data, true, preMultiplyOnLoading);
+    Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.get(), true, preMultiplyOnLoading);
 
     if(pixelBuffer)
     {
@@ -127,7 +139,18 @@ bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, const NPatch
   return false;
 }
 
-void NPatchLoader::Remove(std::size_t id, TextureUploadObserver* textureObserver)
+void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
+{
+  mRemoveQueue.push_back({id, textureObserver});
+
+  if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    mRemoveProcessorRegistered = true;
+    Adaptor::Get().RegisterProcessor(*this, true);
+  }
+}
+
+void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
 {
   int32_t cacheIndex = GetCacheIndexFromId(id);
   if(cacheIndex == INVALID_CACHE_INDEX)
@@ -145,7 +168,22 @@ void NPatchLoader::Remove(std::size_t id, TextureUploadObserver* textureObserver
   }
 }
 
-NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
+void NPatchLoader::Process(bool postProcessor)
+{
+  for(auto& iter : mRemoveQueue)
+  {
+    Remove(iter.first, iter.second);
+  }
+  mRemoveQueue.clear();
+
+  if(Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
+  }
+}
+
+std::shared_ptr<NPatchData> NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
 {
   std::size_t                              hash  = CalculateHash(url.GetUrl());
   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
@@ -164,7 +202,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
         if(mCache[index].mData->GetBorder() == border)
         {
           mCache[index].mReferenceCount++;
-          return mCache[index].mData.get();
+          return mCache[index].mData;
         }
         else
         {
@@ -197,7 +235,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
   // If this is new image loading, make new cache data
   if(infoPtr == nullptr)
   {
-    NPatchInfo info(new NPatchData());
+    NPatchInfo info(std::make_shared<NPatchData>());
     info.mData->SetId(GenerateUniqueNPatchDataId());
     info.mData->SetHash(hash);
     info.mData->SetUrl(url);
@@ -210,7 +248,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
   {
-    NPatchInfo info(new NPatchData());
+    NPatchInfo info(std::make_shared<NPatchData>());
 
     info.mData->SetId(GenerateUniqueNPatchDataId());
     info.mData->SetHash(hash);
@@ -245,7 +283,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
 
   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
 
-  return infoPtr->mData.get();
+  return infoPtr->mData;
 }
 
 } // namespace Internal
index 539009b..7e846e4 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_NPATCH_LOADER_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 // EXTERNAL INCLUDES
 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
+#include <dali/integration-api/processor-interface.h>
 #include <dali/public-api/rendering/texture-set.h>
-#include <memory> // for std::unique_ptr
+#include <memory> // for std::shared_ptr
 #include <string>
+#include <utility> // for std::pair
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/utility/npatch-utilities.h>
@@ -44,7 +46,7 @@ namespace Internal
  * small space and there's not usually a lot of them. Usually N patches are specified in
  * toolkit default style and there is 1-2 per control that are shared across the whole application.
  */
-class NPatchLoader
+class NPatchLoader : public Integration::Processor
 {
 public:
   /**
@@ -69,16 +71,7 @@ public:
    * @param [in] synchronousLoading True if the image will be loaded in synchronous time.
    * @return id of the texture.
    */
-  std::size_t Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading);
-
-  /**
-   * @brief Set loaded PixelBuffer and its information
-   *
-   * @param [in] id cache data id
-   * @param [in] pixelBuffer of loaded image
-   * @param [in] preMultiplied True if the image had pre-multiplied alpha applied
-   */
-  void SetNPatchData(std::size_t id, Devel::PixelBuffer& pixelBuffer, bool preMultiplied);
+  NPatchData::NPatchDataId Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading);
 
   /**
    * @brief Retrieve N patch data matching to an id
@@ -89,21 +82,36 @@ public:
   bool GetNPatchData(const NPatchData::NPatchDataId id, const NPatchData*& data);
 
   /**
-   * @brief Remove a texture matching id.
+   * @brief Request remove a texture matching id.
    * Erase the observer from the observer list of cache if we need.
-   * This API decrease cached NPatchInfo reference.
-   * If the NPatchInfo reference become 0, the textureSet will be reset.
    *
    * @param [in] id cache data id
    * @param [in] textureObserver The NPatchVisual that requested loading.
    */
-  void Remove(std::size_t id, TextureUploadObserver* textureObserver);
+  void RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver);
+
+protected: // Implementation of Processor
+  /**
+   * @copydoc Dali::Integration::Processor::Process()
+   */
+  void Process(bool postProcessor) override;
 
 private:
   NPatchData::NPatchDataId GenerateUniqueNPatchDataId();
 
   int32_t GetCacheIndexFromId(const NPatchData::NPatchDataId id);
 
+  /**
+   * @brief Remove a texture matching id.
+   * Erase the observer from the observer list of cache if we need.
+   * This API decrease cached NPatchInfo reference.
+   * If the NPatchInfo reference become 0, the textureSet will be reset.
+   *
+   * @param [in] id cache data id
+   * @param [in] textureObserver The NPatchVisual that requested loading.
+   */
+  void Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver);
+
 private:
   /**
    * @brief Information of NPatchData
@@ -111,7 +119,7 @@ private:
    */
   struct NPatchInfo
   {
-    NPatchInfo(NPatchData* data)
+    NPatchInfo(std::shared_ptr<NPatchData> data)
     : mData(data),
       mReferenceCount(1u)
     {
@@ -137,7 +145,7 @@ private:
     NPatchInfo(const NPatchInfo& info) = delete;            // Do not use copy constructor
     NPatchInfo& operator=(const NPatchInfo& info) = delete; // Do not use copy assign
 
-    std::unique_ptr<NPatchData> mData;
+    std::shared_ptr<NPatchData> mData;
     std::int16_t                mReferenceCount; ///< The number of N-patch visuals that use this data.
   };
 
@@ -151,7 +159,7 @@ private:
    *                                   image has no alpha channel
    * @return NPatchData pointer that Load function will used.
    */
-  NPatchData* GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad);
+  std::shared_ptr<NPatchData> GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad);
 
 protected:
   /**
@@ -167,6 +175,10 @@ protected:
 private:
   NPatchData::NPatchDataId mCurrentNPatchDataId;
   std::vector<NPatchInfo>  mCache;
+
+  std::vector<std::pair<NPatchData::NPatchDataId, TextureUploadObserver*>> mRemoveQueue; ///< Queue of textures to remove at PostProcess. It will be cleared after PostProcess.
+
+  bool mRemoveProcessorRegistered : 1; ///< Flag if remove processor registered or not.
 };
 
 } // namespace Internal
index d36eeb1..c9367a5 100644 (file)
@@ -239,7 +239,7 @@ void NPatchVisual::DoSetOffScene(Actor& actor)
   {
     if(mId != NPatchData::INVALID_NPATCH_DATA_ID)
     {
-      mLoader.Remove(mId, this);
+      mLoader.RequestRemove(mId, this);
       mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING;
       mId                    = NPatchData::INVALID_NPATCH_DATA_ID;
     }
@@ -319,7 +319,7 @@ NPatchVisual::~NPatchVisual()
     {
       if(mId != NPatchData::INVALID_NPATCH_DATA_ID)
       {
-        mLoader.Remove(mId, this);
+        mLoader.RequestRemove(mId, this);
         mId = NPatchData::INVALID_NPATCH_DATA_ID;
       }
       if(mAuxiliaryTextureId != TextureManager::INVALID_TEXTURE_ID)