Ensured ImageView requests inside ResourceReady signal handler are queued. 07/172407/9
authorDavid Steele <david.steele@samsung.com>
Tue, 13 Mar 2018 20:38:09 +0000 (20:38 +0000)
committerDavid Steele <david.steele@samsung.com>
Thu, 31 Jan 2019 15:46:38 +0000 (15:46 +0000)
Deferring ImageView load requests until after ResourceReady signal
handler.  has completed ensures that attempting to re-load images
doesn't fail to send a second ResourceReady callback.

Now also tries to re-load images that had previously failed.

TextureManager::LoadTexture() now checks for deferred loading when
setting 'loading' flag in output parameters. Note, loading an existing
texture may also cause loading flag to get set - this is to ensure
that the caller, e.g. ImageVisual handles the deferred Complete
callback as if it were asynchronous.

An application can still cause an infinite loop if it doesn't have a max
retry count when attempting to re-load failed images inside the signal
handler. This is considered to be an application bug, not a DALi bug.
( Control::ResourceReady signal is not a one-shot signal).

Change-Id: I2c505623ce5e02d3ae67e6e06fd80d5108dc8ade
Signed-off-by: David Steele <david.steele@samsung.com>
15 files changed:
automated-tests/src/dali-toolkit-internal/CMakeLists.txt
automated-tests/src/dali-toolkit-internal/utc-Dali-TextureManager.cpp
automated-tests/src/dali-toolkit-styling/CMakeLists.txt
automated-tests/src/dali-toolkit-third-party/CMakeLists.txt
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.h
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextureManager.cpp
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/texture-manager-impl.cpp
dali-toolkit/internal/visuals/texture-manager-impl.h
dali-toolkit/internal/visuals/visual-factory-impl.cpp

index 7a71661..746299b 100755 (executable)
@@ -49,6 +49,7 @@ LIST(APPEND TC_SOURCES
    ../dali-toolkit/dali-toolkit-test-utils/toolkit-tts-player.cpp
    ../dali-toolkit/dali-toolkit-test-utils/toolkit-vector-animation-renderer.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dali-test-suite-utils.cpp
+   ../dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dummy-control.cpp
    ../dali-toolkit/dali-toolkit-test-utils/mesh-builder.cpp
    ../dali-toolkit/dali-toolkit-test-utils/test-actor-utils.cpp
index d72bf17..823c7bb 100644 (file)
 
 using namespace Dali::Toolkit::Internal;
 
+
+void utc_dali_toolkit_texture_manager_startup(void)
+{
+  setenv( "LOG_TEXTURE_MANAGER", "3", 1 );
+  test_return_value = TET_UNDEF;
+}
+
+void utc_dali_toolkit_texture_manager_cleanup(void)
+{
+  test_return_value = TET_PASS;
+}
+
 class TestObserver : public Dali::Toolkit::TextureUploadObserver
 {
 public:
index 6b38451..2366711 100644 (file)
@@ -37,6 +37,7 @@ LIST(APPEND TC_SOURCES
    ../dali-toolkit/dali-toolkit-test-utils/toolkit-virtual-keyboard.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dummy-control.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dali-test-suite-utils.cpp
+   ../dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp
    ../dali-toolkit/dali-toolkit-test-utils/test-animation-data.cpp
    ../dali-toolkit/dali-toolkit-test-utils/test-button.cpp
    ../dali-toolkit/dali-toolkit-test-utils/test-application.cpp
index e9ff79a..a5fde09 100644 (file)
@@ -26,6 +26,7 @@ LIST(APPEND TC_SOURCES
    ../dali-toolkit/dali-toolkit-test-utils/toolkit-timer.cpp
    ../dali-toolkit/dali-toolkit-test-utils/toolkit-tts-player.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dali-test-suite-utils.cpp
+   ../dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp
    ../dali-toolkit/dali-toolkit-test-utils/dummy-control.cpp
    ../dali-toolkit/dali-toolkit-test-utils/mesh-builder.cpp
    ../dali-toolkit/dali-toolkit-test-utils/test-actor-utils.cpp
index bd9848f..1ef55f9 100755 (executable)
@@ -104,6 +104,7 @@ LIST(APPEND TC_SOURCES
   dali-toolkit-test-utils/toolkit-web-engine.cpp
   dali-toolkit-test-utils/toolkit-trigger-event-factory.cpp
   dali-toolkit-test-utils/dali-test-suite-utils.cpp
+  dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp
   dali-toolkit-test-utils/dummy-control.cpp
   dali-toolkit-test-utils/layout-utils.cpp
   dali-toolkit-test-utils/mesh-builder.cpp
diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/dali-toolkit-test-suite-utils.cpp
new file mode 100644 (file)
index 0000000..94c9530
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+#include "dali-toolkit-test-suite-utils.h"
+
+
+std::ostream& operator<<( std::ostream& ostream, Dali::Toolkit::Visual::ResourceStatus status )
+{
+  switch(status)
+  {
+    case Dali::Toolkit::Visual::ResourceStatus::PREPARING:
+    {
+      ostream << "PREPARING";
+      break;
+    }
+    case Dali::Toolkit::Visual::ResourceStatus::READY:
+    {
+      ostream << "READY";
+      break;
+    }
+    case Dali::Toolkit::Visual::ResourceStatus::FAILED:
+    {
+      ostream << "FAILED";
+      break;
+    }
+  }
+  return ostream;
+}
index 32dac4b..013d2c6 100644 (file)
@@ -2,7 +2,7 @@
 #define __DALI_TOOLKIT_TEST_SUITE_UTILS_H__
 
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2019 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.
  *
  */
 
+#include <iostream>
+#include <dali-toolkit/public-api/visuals/visual-properties.h>
+
+// Put any toolkit specific operators for DALI_TEST_CHECK template here, before dali-test-suite-utils.h.
+
+std::ostream& operator<<( std::ostream& ostream, Dali::Toolkit::Visual::ResourceStatus status );
+
 // INTERNAL INCLUDES
 
 #include <dali-test-suite-utils.h>
index 5b8a324..02f3d8f 100644 (file)
@@ -77,7 +77,7 @@ bool EventThreadCallback::WaitingForTrigger()
   struct timespec now;
   clock_gettime( CLOCK_REALTIME, &now );
   if( now.tv_nsec < 999900000 ) // 999, 900, 000
-    now.tv_nsec += 100000;
+    now.tv_nsec += 1000;
   else
   {
     now.tv_sec += 1;
@@ -124,6 +124,10 @@ bool WaitForEventThreadTrigger( int triggerCount, int timeoutInSeconds )
           Dali::CallbackBase::Execute( *callback );
           triggerCount--;
         }
+        if( triggerCount <= 0 )
+        {
+          break;
+        }
       }
     }
     clock_gettime( CLOCK_REALTIME, &now );
index 29370b7..7d1ea16 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2019 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.
@@ -1841,6 +1841,8 @@ int UtcDaliImageViewCustomShader(void)
     application.SendNotification();
     application.Render();
 
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+
     Renderer renderer = imageView.GetRendererAt( 0 );
     Shader shader2 = renderer.GetShader();
     Property::Value value = shader2.GetProperty( Shader::Property::PROGRAM );
@@ -1873,6 +1875,7 @@ int UtcDaliImageViewCustomShader(void)
 
     application.SendNotification();
     application.Render();
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
 
     Renderer renderer = imageView.GetRendererAt( 0 );
     Shader shader2 = renderer.GetShader();
@@ -1910,6 +1913,7 @@ int UtcDaliImageViewCustomShader(void)
 
     application.SendNotification();
     application.Render();
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
 
     Renderer renderer = imageView.GetRendererAt( 0 );
     Shader shader2 = renderer.GetShader();
@@ -1947,6 +1951,7 @@ int UtcDaliImageViewCustomShader(void)
 
     application.SendNotification();
     application.Render();
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
 
     Renderer renderer = imageView.GetRendererAt( 0 );
     Shader shader2 = renderer.GetShader();
@@ -1963,3 +1968,60 @@ int UtcDaliImageViewCustomShader(void)
 
   END_TEST;
 }
+
+
+namespace
+{
+static int gFailCounter = 0;
+const int MAX_RETRIES(3);
+
+void ReloadImage(ImageView imageView)
+{
+  Property::Map imageImmediateLoadingMap;
+  imageImmediateLoadingMap[ ImageVisual::Property::URL ] = "Non-existant-image.jpg";
+  imageImmediateLoadingMap[ ImageVisual::Property::LOAD_POLICY ] =  ImageVisual::LoadPolicy::IMMEDIATE;
+
+  tet_infoline("Immediate load an image");
+  imageView.SetProperty( ImageView::Property::IMAGE, imageImmediateLoadingMap );
+}
+
+void ResourceFailedReload( Control control )
+{
+  gFailCounter++;
+  if( gFailCounter < MAX_RETRIES )
+  {
+    ReloadImage(ImageView::DownCast(control));
+  }
+}
+}
+
+int UtcDaliImageViewReloadFailedOnResourceReadySignal(void)
+{
+  tet_infoline("Test reloading failed image from within signal handler.");
+
+  ToolkitTestApplication application;
+
+  gFailCounter = 0;
+
+  ImageView imageView = ImageView::New();
+  imageView.ResourceReadySignal().Connect( &ResourceFailedReload );
+  DALI_TEST_EQUALS( gFailCounter, 0, TEST_LOCATION );
+  ReloadImage(imageView);
+
+  // loading started, this waits for the loader thread to complete
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+  application.SendNotification();
+
+  DALI_TEST_EQUALS( gFailCounter, 1, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+  application.SendNotification();
+
+  DALI_TEST_EQUALS( gFailCounter, 2, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+  application.SendNotification();
+  DALI_TEST_EQUALS( gFailCounter, 3, TEST_LOCATION );
+
+  END_TEST;
+}
index 90dd9e7..a15ecf6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2019 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.
@@ -16,6 +16,7 @@
 
 #include <iostream>
 #include <stdlib.h>
+#include <vector>
 #include <dali-toolkit-test-suite-utils.h>
 #include <toolkit-timer.h>
 #include <toolkit-event-thread-callback.h>
@@ -43,6 +44,7 @@ void dali_image_visual_cleanup(void)
 namespace
 {
 const char* TEST_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR  "/gallery-small-1.jpg";
+const char* TEST_BROKEN_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR  "/a-random-nonimage.jpg";
 const char* TEST_LARGE_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR "/tbcol.png";
 const char* TEST_SMALL_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/icon-edit.png";
 const char* TEST_REMOTE_IMAGE_FILE_NAME = "https://www.tizen.org/sites/all/themes/tizen_theme/logo.png";
@@ -53,10 +55,15 @@ const char* TEST_ROTATED_IMAGE =  TEST_RESOURCE_DIR  "/keyboard-Landscape.jpg";
 
 
 bool gResourceReadySignalFired = false;
-
+std::vector<int> gReadyIds = {};
 void ResourceReadySignal( Control control )
 {
   gResourceReadySignalFired = true;
+  gReadyIds.push_back(control.GetId());
+}
+void ClearReadyIds()
+{
+  gReadyIds.clear();
 }
 
 Actor CreateActorWithImageVisual(const Property::Map& map)
@@ -2178,3 +2185,97 @@ int UtcDaliImageVisualCustomShader(void)
 
   END_TEST;
 }
+
+
+void ResourceReadyLoadNext( Control control )
+{
+  static int callNumber = 0;
+
+  gResourceReadySignalFired = true;
+  gReadyIds.push_back(control.GetId());
+
+  if( callNumber == 0 )
+  {
+    DALI_TEST_EQUALS( control.GetVisualResourceStatus(DummyControl::Property::TEST_VISUAL), Toolkit::Visual::ResourceStatus::FAILED, TEST_LOCATION );
+
+    tet_infoline( "Create visual with loaded image from within the signal handler" );
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base imageVisual = factory.CreateVisual( TEST_IMAGE_FILE_NAME, ImageDimensions{20,30} );
+
+    Impl::DummyControl& controlImpl = static_cast<Impl::DummyControl&>(control.GetImplementation());
+    controlImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, imageVisual ); // This should trigger another signal.
+    callNumber = 1;
+  }
+  else
+  {
+    tet_infoline( "3rd signal called" );
+    DALI_TEST_CHECK(true);
+  }
+}
+
+int UtcDaliImageVisualLoadReady01(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "UtcDaliImageVisualLoadReady01");
+  tet_infoline( "First part:  Load an image visual for one resource, then another image visual for a second resource.");
+  tet_infoline( "Second part, In the ready signal for the second image visual, add a 3rd visual with the first URL" );
+  tet_infoline( "Should get a ready signal for all three visuals");
+
+  ClearReadyIds();
+
+  tet_infoline( "Create a control and connect to resource ready signal" );
+  DummyControl actor = DummyControl::New(true);
+  int actor1Id = actor.GetId();
+  actor.ResourceReadySignal().Connect( &ResourceReadySignal);
+  Impl::DummyControl& dummyImpl = static_cast<Impl::DummyControl&>(actor.GetImplementation());
+  actor.SetSize(200.f, 200.f);
+  Stage::GetCurrent().Add(actor);
+
+  tet_infoline( "Create visual with IMMEDIATE load policy" );
+  Visual::Base imageVisual1 = CreateVisualWithPolicy( TEST_IMAGE_FILE_NAME, ImageVisual::Property::LOAD_POLICY, ImageVisual::LoadPolicy::IMMEDIATE );
+
+  tet_infoline( "Registering visual allows control to get a signal once loaded even if visual not enabled( staged )" );
+  dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, imageVisual1 );
+
+
+  tet_infoline( "Allow image time to load" );
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+  application.SendNotification();
+  application.Render();
+
+  tet_infoline( "Testing texture is loaded and resource ready signal fired" );
+  DALI_TEST_EQUALS( gResourceReadySignalFired, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( gReadyIds[0], actor1Id, TEST_LOCATION );
+
+
+  tet_infoline( "Original control correctly signalled, now testing failing image" );
+
+  gResourceReadySignalFired = false; // Reset signal check ready for testing next Control
+  ClearReadyIds();
+
+  Visual::Base imageVisual2 = CreateVisualWithPolicy( TEST_BROKEN_IMAGE_FILE_NAME, ImageVisual::Property::LOAD_POLICY, ImageVisual::LoadPolicy::IMMEDIATE );
+
+  DummyControl actor2 = DummyControl::New(true);
+  int actor2Id = actor2.GetId();
+  Impl::DummyControl& dummyImpl2 = static_cast<Impl::DummyControl&>(actor2.GetImplementation());
+  actor2.ResourceReadySignal().Connect( &ResourceReadyLoadNext);
+
+  tet_infoline( "Registering visual this should trigger the ready signal when the image fails to load" );
+  dummyImpl2.RegisterVisual( DummyControl::Property::TEST_VISUAL, imageVisual2 );
+
+  actor2.SetSize(200.f, 200.f);
+  Stage::GetCurrent().Add(actor2);
+
+  tet_infoline( "Wait for loading thread to finish");
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+  DALI_TEST_EQUALS( gResourceReadySignalFired, true, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( gReadyIds[0], actor2Id, TEST_LOCATION);
+
+  tet_infoline( "Check for 3rd signal");
+  application.SendNotification();
+  DALI_TEST_EQUALS( gReadyIds.size(), 2, TEST_LOCATION );
+  DALI_TEST_EQUALS( gReadyIds[1], actor2Id, TEST_LOCATION);
+
+  END_TEST;
+}
index 89a95ba..72370db 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2019 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.
@@ -26,7 +26,6 @@ using namespace Dali::Toolkit;
 
 namespace
 {
-
 } // namespace
 
 
@@ -152,6 +151,3 @@ int UtcDaliTextureManagerRemoveN(void)
 
   END_TEST;
 }
-
-
-
index 3f4e763..bbef5e4 100644 (file)
@@ -811,6 +811,7 @@ void ImageVisual::DoSetOffStage( Actor& actor )
   if( mReleasePolicy == Toolkit::ImageVisual::ReleasePolicy::DETACHED )
   {
     RemoveTexture(); // If INVALID_TEXTURE_ID then removal will be attempted on atlas
+    mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING;
   }
 
   if( mImageUrl.IsValid() )
index 1ea95a9..7847924 100644 (file)
@@ -1,5 +1,5 @@
  /*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2019 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.
@@ -79,6 +79,16 @@ namespace
 
 #ifdef DEBUG_ENABLED
 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
+
+#define GET_LOAD_STATE_STRING( loadState ) \
+  loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :             \
+    loadState == TextureManager::LOADING ? "LOADING" :                   \
+    loadState == TextureManager::LOAD_FINISHED ? "LOAD_FINISHED" :       \
+    loadState == TextureManager::WAITING_FOR_MASK ? "WAITING_FOR_MASK" : \
+    loadState == TextureManager::UPLOADED ? "UPLOADED" :                 \
+    loadState == TextureManager::CANCELLED ? "CANCELLED" :               \
+    loadState == TextureManager::LOAD_FAILED ? "LOAD_FAILED" : "Unknown"
+
 #endif
 
 const uint32_t      DEFAULT_ATLAS_SIZE( 1024u );                     ///< This size can fit 8 by 8 images of average size 128 * 128
@@ -117,8 +127,10 @@ TextureManager::TextureManager()
   mAsyncRemoteLoaders( GetNumberOfRemoteLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
   mExternalTextures(),
   mLifecycleObservers(),
+  mLoadQueue(),
   mBrokenImageUrl(""),
-  mCurrentTextureId( 0 )
+  mCurrentTextureId( 0 ),
+  mQueueLoadFlag(false)
 {
 }
 
@@ -242,13 +254,18 @@ TextureSet TextureManager::LoadTexture(
       }
 
       TextureManager::LoadState loadState = GetTextureStateInternal( textureId );
-      loadingStatus = ( loadState == TextureManager::LOADING );
-
       if( loadState == TextureManager::UPLOADED )
       {
         // UploadComplete has already been called - keep the same texture set
         textureSet = GetTextureSet( textureId );
       }
+
+      // If we are loading the texture, or waiting for the ready signal handler to complete, inform
+      // caller that they need to wait.
+      loadingStatus = ( loadState == TextureManager::LOADING ||
+                        loadState == TextureManager::NOT_STARTED ||
+                        mQueueLoadFlag );
+
     }
     else
     {
@@ -345,7 +362,7 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
       ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
     }
     textureId = mTextureInfoContainer[ cacheIndex ].textureId;
-    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture id@%d, textureId=%d\n",
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::General, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture id@%d, textureId=%d\n",
                    url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
 
@@ -360,7 +377,7 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
                                                   preMultiply ) );
     cacheIndex = mTextureInfoContainer.size() - 1u;
 
-    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n",
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::General, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n",
                    url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
 
@@ -372,11 +389,8 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
   textureInfo.storageType = storageType;
   textureInfo.orientationCorrection = orientationCorrection;
 
-  DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
-                 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
-                 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
-                 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
-                 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
+  DALI_LOG_INFO( gTextureManagerLogFilter, Debug::General, "TextureInfo loadState:%s\n",
+                 GET_LOAD_STATE_STRING(textureInfo.loadState ) );
 
   // Force reloading of texture by setting loadState unless already loading or cancelled.
   if ( TextureManager::ReloadPolicy::FORCED == reloadPolicy && TextureManager::LOADING != textureInfo.loadState &&
@@ -394,8 +408,7 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
     case TextureManager::LOAD_FAILED: // Failed notifies observer which then stops observing.
     case TextureManager::NOT_STARTED:
     {
-      LoadTexture( textureInfo );
-      ObserveTexture( textureInfo, observer );
+      LoadOrQueueTexture( textureInfo, observer ); // If called inside NotifyObservers, queues until afterwards
       break;
     }
     case TextureManager::LOADING:
@@ -407,11 +420,7 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
     {
       if( observer )
       {
-        // The Texture has already loaded. The other observers have already been notified.
-        // We need to send a "late" loaded notification for this observer.
-        observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
-                                  textureInfo.useAtlas, textureInfo.atlasRect,
-                                  textureInfo.preMultiplied );
+        LoadOrQueueTexture( textureInfo, observer );
       }
       break;
     }
@@ -440,12 +449,10 @@ void TextureManager::Remove( const TextureManager::TextureId textureId )
   {
     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
 
-    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
-                   textureId, textureInfoIndex,
-                   textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
-                   textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
-                   textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
-                   textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise,
+                   "TextureManager::Remove(%d) url:%s\n  cacheIdx:%d loadState:%s\n",
+                   textureId, textureInfo.url.GetUrl().c_str(),
+                   textureInfoIndex, GET_LOAD_STATE_STRING( textureInfo.loadState ) );
 
     // Decrement the reference count and check if this is the last user of this Texture.
     if( --textureInfo.referenceCount <= 0 )
@@ -624,32 +631,101 @@ void TextureManager::RemoveObserver( TextureManager::LifecycleObserver& observer
   DALI_ASSERT_DEBUG(endIter != mLifecycleObservers.End());
 }
 
+void TextureManager::LoadOrQueueTexture( TextureInfo& textureInfo, TextureUploadObserver* observer )
+{
+  switch( textureInfo.loadState )
+  {
+    case NOT_STARTED:
+    case LOAD_FAILED:
+    {
+      if( mQueueLoadFlag )
+      {
+        QueueLoadTexture( textureInfo, observer );
+      }
+      else
+      {
+        LoadTexture( textureInfo, observer );
+      }
+      break;
+    }
+    case LOADING:
+    case UPLOADED:
+    {
+      if( mQueueLoadFlag )
+      {
+        QueueLoadTexture( textureInfo, observer );
+      }
+      else
+      {
+        // The Texture has already loaded. The other observers have already been notified.
+        // We need to send a "late" loaded notification for this observer.
+        observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
+                                  textureInfo.useAtlas, textureInfo.atlasRect,
+                                  textureInfo.preMultiplied );
+      }
+    }
+    case CANCELLED:
+    case LOAD_FINISHED:
+    case WAITING_FOR_MASK:
+    {
+      break;
+    }
+  }
+}
+
+void TextureManager::QueueLoadTexture( TextureInfo& textureInfo, TextureUploadObserver* observer )
+{
+  auto textureId = textureInfo.textureId;
+  mLoadQueue.PushBack( LoadQueueElement( textureId, observer) );
+}
 
-bool TextureManager::LoadTexture( TextureInfo& textureInfo )
+void TextureManager::LoadTexture( TextureInfo& textureInfo, TextureUploadObserver* observer )
 {
-  bool success = true;
+  DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::LoadTexture(): url:%s sync:%s\n",
+                 textureInfo.url.GetUrl().c_str(), textureInfo.loadSynchronously?"T":"F" );
 
-  if( textureInfo.loadState == NOT_STARTED )
+  textureInfo.loadState = LOADING;
+  if( !textureInfo.loadSynchronously )
   {
-    textureInfo.loadState = LOADING;
+    auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
+    auto loadingHelperIt = loadersContainer.GetNext();
+    DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
+    loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
+                          textureInfo.desiredSize, textureInfo.fittingMode,
+                          textureInfo.samplingMode, textureInfo.orientationCorrection );
+  }
+  ObserveTexture( textureInfo, observer );
+}
 
-    if( !textureInfo.loadSynchronously )
+void TextureManager::ProcessQueuedTextures()
+{
+  for( auto&& element : mLoadQueue )
+  {
+    int cacheIndex = GetCacheIndexFromId( element.mTextureId );
+    if( cacheIndex != INVALID_CACHE_INDEX )
     {
-      auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
-      auto loadingHelperIt = loadersContainer.GetNext();
-      DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
-      loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
-                            textureInfo.desiredSize, textureInfo.fittingMode,
-                            textureInfo.samplingMode, textureInfo.orientationCorrection );
+      TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
+      if( textureInfo.loadState == UPLOADED )
+      {
+        element.mObserver->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
+                                           textureInfo.useAtlas, textureInfo.atlasRect,
+                                           textureInfo.preMultiplied );
+      }
+      else
+      {
+        LoadTexture( textureInfo, element.mObserver );
+      }
     }
   }
-
-  return success;
+  mLoadQueue.Clear();
 }
 
 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
                                      TextureUploadObserver* observer )
 {
+  DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::ObserveTexture(): url:%s observer:%p\n",
+                 textureInfo.url.GetUrl().c_str(), observer );
+
   if( observer )
   {
     textureInfo.observerList.PushBack( observer );
@@ -673,7 +749,9 @@ void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingCo
       {
         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
 
-        DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
+        DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise,
+                       "  textureId:%d Url:%s CacheIndex:%d LoadState: %d\n",
+                       textureInfo.textureId, textureInfo.url.GetUrl().c_str(), cacheIndex, textureInfo.loadState );
 
         if( textureInfo.loadState != CANCELLED )
         {
@@ -788,12 +866,11 @@ void TextureManager::ApplyMask(
   }
 }
 
-
 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
 {
   if( textureInfo.useAtlas != USE_ATLAS )
   {
-    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::General, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
 
     // If the texture doesn't have an alpha channel, can't pre-multiply it.
     // Ensure that we don't change the load parameter (it's used for hashing), and instead set
@@ -828,53 +905,50 @@ void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
 
   // If there is an observer: Notify the load is complete, whether successful or not,
   // and erase it from the list
-  unsigned int observerCount = textureInfo.observerList.Count();
   TextureInfo* info = &textureInfo;
 
-  while( observerCount )
+  mQueueLoadFlag = true;
+
+  while( info->observerList.Count() )
   {
     TextureUploadObserver* observer = info->observerList[0];
 
     // During UploadComplete() a Control ResourceReady() signal is emitted.
     // During that signal the app may add remove /add Textures (e.g. via
-    // ImageViews).  At this point no more observers can be added to the
-    // observerList, because textureInfo.loadState = UPLOADED. However it is
-    // possible for observers to be removed, hence we check the observer list
-    // count every iteration.
-
-    // The reference to the textureInfo struct can also become invalidated,
-    // because new load requests can modify the mTextureInfoContainer list
-    // (e.g. if more requests are pushed back it can cause the list to be
-    // resized invalidating the reference to the TextureInfo ).
+    // ImageViews).
+    // It is possible for observers to be removed from the observer list,
+    // and it is also possible for the mTextureInfoContainer to be modified,
+    // invalidating the reference to the textureInfo struct.
+    // Texture load requests for the same URL are deferred until the end of this
+    // method.
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "NotifyObservers() url:%s loadState:%s\n",
+                   textureInfo.url.GetUrl().c_str(), GET_LOAD_STATE_STRING(textureInfo.loadState ) );
+
     observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect,
                               info->preMultiplied );
     observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
 
-    // Get the textureInfo from the container again as it may have been
-    // invalidated,
-
+    // Get the textureInfo from the container again as it may have been invalidated.
     int textureInfoIndex = GetCacheIndexFromId( textureId );
     if( textureInfoIndex == INVALID_CACHE_INDEX)
     {
-      return; // texture has been removed - can stop.
+      break; // texture has been removed - can stop.
     }
-
     info = &mTextureInfoContainer[ textureInfoIndex ];
-    observerCount = info->observerList.Count();
-    if ( observerCount > 0 )
+
+    // remove the observer that was just triggered if it's still in the list
+    for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
     {
-      // remove the observer that was just triggered if it's still in the list
-      for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
+      if( *j == observer )
       {
-        if( *j == observer )
-        {
-          info->observerList.Erase( j );
-          observerCount--;
-          break;
-        }
+        info->observerList.Erase( j );
+        break;
       }
     }
   }
+
+  mQueueLoadFlag = false;
+  ProcessQueuedTextures();
 }
 
 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
index 6df17c6..502cf11 100755 (executable)
@@ -424,6 +424,8 @@ private:
 
   typedef size_t TextureHash; ///< The type used to store the hash used for Texture caching.
 
+  // Structs:
+
   /**
    * @brief This struct is used to manage the life-cycle of Texture loading and caching.
    */
@@ -495,7 +497,20 @@ private:
     bool preMultiplied:1;          ///< true if the image's color was multiplied by it's alpha
   };
 
-  // Structs:
+  /**
+   * Structure to hold info about a texture load queued during NotifyObservers
+   */
+  struct LoadQueueElement
+  {
+    LoadQueueElement( TextureId textureId, TextureUploadObserver* observer )
+    : mTextureId( textureId ),
+      mObserver( observer )
+    {
+    }
+
+    TextureId mTextureId; ///< The texture id of the requested load.
+    TextureUploadObserver* mObserver; ///< Observer of texture load.
+  };
 
   /**
    * Struct to hold information about a requested Async load.
@@ -519,16 +534,35 @@ private:
   typedef std::vector<TextureInfo>      TextureInfoContainerType;       ///< The container type used to manage the life-cycle and caching of Textures
 
   /**
+   * @brief Initiate a load or queue load if NotifyObservers is invoking callbacks
+   * @param[in] textureInfo The TextureInfo struct associated with the Texture
+   * @param[in] observer The observer wishing to observe the texture upload
+   */
+  void LoadOrQueueTexture( TextureInfo& textureInfo, TextureUploadObserver* observer );
+
+  /**
+   * @brief Queue a texture load to be subsequently handled by ProcessQueuedTextures.
+   * @param[in] textureInfo The TextureInfo struct associated with the Texture
+   * @param[in] observer The observer wishing to observe the texture upload
+   */
+  void QueueLoadTexture( TextureInfo& textureInfo, TextureUploadObserver* observer );
+
+  /**
    * @brief Used internally to initiate a load.
    * @param[in] textureInfo The TextureInfo struct associated with the Texture
-   * @return                True if the load was initiated
+   * @param[in] observer The observer wishing to observe the texture upload
+   */
+  void LoadTexture( TextureInfo& textureInfo, TextureUploadObserver* observer );
+
+  /**
+   * @brief Initiate load of textures queued whilst NotifyObservers invoking callbacks.
    */
-  bool LoadTexture( TextureInfo& textureInfo );
+  void ProcessQueuedTextures();
 
   /**
    * Add the observer to the observer list
    * @param[in] textureInfo The TextureInfo struct associated with the texture
-   * observer The observer wishing to observe the texture upload
+   * @param[in] observer The observer wishing to observe the texture upload
    */
   void ObserveTexture( TextureInfo & textureInfo, TextureUploadObserver* observer );
 
@@ -747,8 +781,10 @@ private:  // Member Variables:
   RoundRobinContainerView< AsyncLoadingHelper > mAsyncRemoteLoaders;   ///< The Asynchronous image loaders used to provide all remote async loads
   std::vector< ExternalTextureInfo >            mExternalTextures;     ///< Externally provided textures
   Dali::Vector<LifecycleObserver*>              mLifecycleObservers;   ///< Lifecycle observers of texture manager
+  Dali::Vector<LoadQueueElement>                mLoadQueue;            ///< Queue of textures to load after NotifyObservers
   std::string                                   mBrokenImageUrl;       ///< Broken image url
   TextureId                                     mCurrentTextureId;     ///< The current value used for the unique Texture Id generation
+  bool                                          mQueueLoadFlag;        ///< Flag that causes Load Textures to be queued.
 };
 
 
index d30729b..2c8183c 100644 (file)
@@ -60,6 +60,10 @@ namespace Internal
 namespace
 {
 
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_CONTROL_VISUALS");
+#endif
+
 BaseHandle Create()
 {
   BaseHandle handle = Toolkit::VisualFactory::Get();
@@ -273,9 +277,19 @@ Toolkit::Visual::Base VisualFactory::CreateVisual( const Property::Map& property
     }
   }
 
+  DALI_LOG_INFO( gLogFilter, Debug::Concise, "VisualFactory::CreateVisual( VisualType:%s %s%s)\n",
+                 Scripting::GetEnumerationName<Toolkit::DevelVisual::Type>( visualType,
+                                                                            VISUAL_TYPE_TABLE,
+                                                                            VISUAL_TYPE_TABLE_COUNT ),
+                 visualType==Toolkit::DevelVisual::IMAGE?"url:":"",
+                 visualType==Toolkit::DevelVisual::IMAGE ?
+                 propertyMap.Find( Toolkit::ImageVisual::Property::URL, IMAGE_URL_NAME)->Get<std::string>().c_str()
+                 :"" );
+
+
   if( !visualPtr )
   {
-    DALI_LOG_ERROR( "Renderer type unknown\n" );
+    DALI_LOG_ERROR( "VisualType unknown\n" );
   }
 
   if( mDebugEnabled && visualType !=  Toolkit::DevelVisual::WIREFRAME )