BackgroundLoader.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #ifdef URHO3D_THREADING
  4. #include "../Precompiled.h"
  5. #include "../Core/Context.h"
  6. #include "../Core/Profiler.h"
  7. #include "../IO/Log.h"
  8. #include "../Resource/BackgroundLoader.h"
  9. #include "../Resource/ResourceCache.h"
  10. #include "../Resource/ResourceEvents.h"
  11. #include "../DebugNew.h"
  12. namespace Urho3D
  13. {
  14. BackgroundLoader::BackgroundLoader(ResourceCache* owner) :
  15. owner_(owner)
  16. {
  17. }
  18. BackgroundLoader::~BackgroundLoader()
  19. {
  20. MutexLock lock(backgroundLoadMutex_);
  21. backgroundLoadQueue_.Clear();
  22. }
  23. void BackgroundLoader::ThreadFunction()
  24. {
  25. URHO3D_PROFILE_THREAD("BackgroundLoader Thread");
  26. while (shouldRun_)
  27. {
  28. backgroundLoadMutex_.Acquire();
  29. // Search for a queued resource that has not been loaded yet
  30. HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator i = backgroundLoadQueue_.Begin();
  31. while (i != backgroundLoadQueue_.End())
  32. {
  33. if (i->second_.resource_->GetAsyncLoadState() == ASYNC_QUEUED)
  34. break;
  35. else
  36. ++i;
  37. }
  38. if (i == backgroundLoadQueue_.End())
  39. {
  40. // No resources to load found
  41. backgroundLoadMutex_.Release();
  42. Time::Sleep(5);
  43. }
  44. else
  45. {
  46. BackgroundLoadItem& item = i->second_;
  47. Resource* resource = item.resource_;
  48. // We can be sure that the item is not removed from the queue as long as it is in the
  49. // "queued" or "loading" state
  50. backgroundLoadMutex_.Release();
  51. bool success = false;
  52. SharedPtr<File> file = owner_->GetFile(resource->GetName(), item.sendEventOnFailure_);
  53. if (file)
  54. {
  55. resource->SetAsyncLoadState(ASYNC_LOADING);
  56. success = resource->BeginLoad(*file);
  57. }
  58. // Process dependencies now
  59. // Need to lock the queue again when manipulating other entries
  60. Pair<StringHash, StringHash> key = MakePair(resource->GetType(), resource->GetNameHash());
  61. backgroundLoadMutex_.Acquire();
  62. if (item.dependents_.Size())
  63. {
  64. for (HashSet<Pair<StringHash, StringHash>>::Iterator i = item.dependents_.Begin();
  65. i != item.dependents_.End(); ++i)
  66. {
  67. HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator j = backgroundLoadQueue_.Find(*i);
  68. if (j != backgroundLoadQueue_.End())
  69. j->second_.dependencies_.Erase(key);
  70. }
  71. item.dependents_.Clear();
  72. }
  73. resource->SetAsyncLoadState(success ? ASYNC_SUCCESS : ASYNC_FAIL);
  74. backgroundLoadMutex_.Release();
  75. }
  76. }
  77. }
  78. bool BackgroundLoader::QueueResource(StringHash type, const String& name, bool sendEventOnFailure, Resource* caller)
  79. {
  80. StringHash nameHash(name);
  81. Pair<StringHash, StringHash> key = MakePair(type, nameHash);
  82. MutexLock lock(backgroundLoadMutex_);
  83. // Check if already exists in the queue
  84. if (backgroundLoadQueue_.Find(key) != backgroundLoadQueue_.End())
  85. return false;
  86. BackgroundLoadItem& item = backgroundLoadQueue_[key];
  87. item.sendEventOnFailure_ = sendEventOnFailure;
  88. // Make sure the pointer is non-null and is a Resource subclass
  89. item.resource_ = DynamicCast<Resource>(owner_->GetContext()->CreateObject(type));
  90. if (!item.resource_)
  91. {
  92. URHO3D_LOGERROR("Could not load unknown resource type " + String(type));
  93. if (sendEventOnFailure && Thread::IsMainThread())
  94. {
  95. using namespace UnknownResourceType;
  96. VariantMap& eventData = owner_->GetEventDataMap();
  97. eventData[P_RESOURCETYPE] = type;
  98. owner_->SendEvent(E_UNKNOWNRESOURCETYPE, eventData);
  99. }
  100. backgroundLoadQueue_.Erase(key);
  101. return false;
  102. }
  103. URHO3D_LOGDEBUG("Background loading resource " + name);
  104. item.resource_->SetName(name);
  105. item.resource_->SetAsyncLoadState(ASYNC_QUEUED);
  106. // If this is a resource calling for the background load of more resources, mark the dependency as necessary
  107. if (caller)
  108. {
  109. Pair<StringHash, StringHash> callerKey = MakePair(caller->GetType(), caller->GetNameHash());
  110. HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator j = backgroundLoadQueue_.Find(callerKey);
  111. if (j != backgroundLoadQueue_.End())
  112. {
  113. BackgroundLoadItem& callerItem = j->second_;
  114. item.dependents_.Insert(callerKey);
  115. callerItem.dependencies_.Insert(key);
  116. }
  117. else
  118. URHO3D_LOGWARNING("Resource " + caller->GetName() +
  119. " requested for a background loaded resource but was not in the background load queue");
  120. }
  121. // Start the background loader thread now
  122. if (!IsStarted())
  123. Run();
  124. return true;
  125. }
  126. void BackgroundLoader::WaitForResource(StringHash type, StringHash nameHash)
  127. {
  128. backgroundLoadMutex_.Acquire();
  129. // Check if the resource in question is being background loaded
  130. Pair<StringHash, StringHash> key = MakePair(type, nameHash);
  131. HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator i = backgroundLoadQueue_.Find(key);
  132. if (i != backgroundLoadQueue_.End())
  133. {
  134. backgroundLoadMutex_.Release();
  135. {
  136. Resource* resource = i->second_.resource_;
  137. HiresTimer waitTimer;
  138. bool didWait = false;
  139. for (;;)
  140. {
  141. unsigned numDeps = i->second_.dependencies_.Size();
  142. AsyncLoadState state = resource->GetAsyncLoadState();
  143. if (numDeps > 0 || state == ASYNC_QUEUED || state == ASYNC_LOADING)
  144. {
  145. didWait = true;
  146. Time::Sleep(1);
  147. }
  148. else
  149. break;
  150. }
  151. if (didWait)
  152. URHO3D_LOGDEBUG("Waited " + String(waitTimer.GetUSec(false) / 1000) + " ms for background loaded resource " +
  153. resource->GetName());
  154. }
  155. // This may take a long time and may potentially wait on other resources, so it is important we do not hold the mutex during this
  156. FinishBackgroundLoading(i->second_);
  157. backgroundLoadMutex_.Acquire();
  158. backgroundLoadQueue_.Erase(i);
  159. backgroundLoadMutex_.Release();
  160. }
  161. else
  162. backgroundLoadMutex_.Release();
  163. }
  164. void BackgroundLoader::FinishResources(int maxMs)
  165. {
  166. if (IsStarted())
  167. {
  168. HiresTimer timer;
  169. backgroundLoadMutex_.Acquire();
  170. for (HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator i = backgroundLoadQueue_.Begin();
  171. i != backgroundLoadQueue_.End();)
  172. {
  173. Resource* resource = i->second_.resource_;
  174. unsigned numDeps = i->second_.dependencies_.Size();
  175. AsyncLoadState state = resource->GetAsyncLoadState();
  176. if (numDeps > 0 || state == ASYNC_QUEUED || state == ASYNC_LOADING)
  177. ++i;
  178. else
  179. {
  180. // Finishing a resource may need it to wait for other resources to load, in which case we can not
  181. // hold on to the mutex
  182. backgroundLoadMutex_.Release();
  183. FinishBackgroundLoading(i->second_);
  184. backgroundLoadMutex_.Acquire();
  185. i = backgroundLoadQueue_.Erase(i);
  186. }
  187. // Break when the time limit passed so that we keep sufficient FPS
  188. if (timer.GetUSec(false) >= maxMs * 1000LL)
  189. break;
  190. }
  191. backgroundLoadMutex_.Release();
  192. }
  193. }
  194. unsigned BackgroundLoader::GetNumQueuedResources() const
  195. {
  196. MutexLock lock(backgroundLoadMutex_);
  197. return backgroundLoadQueue_.Size();
  198. }
  199. void BackgroundLoader::FinishBackgroundLoading(BackgroundLoadItem& item)
  200. {
  201. Resource* resource = item.resource_;
  202. bool success = resource->GetAsyncLoadState() == ASYNC_SUCCESS;
  203. // If BeginLoad() phase was successful, call EndLoad() and get the final success/failure result
  204. if (success)
  205. {
  206. #ifdef URHO3D_TRACY_PROFILING
  207. URHO3D_PROFILE_COLOR(FinishBackgroundLoading, URHO3D_PROFILE_RESOURCE_COLOR);
  208. String profileBlockName("Finish" + resource->GetTypeName());
  209. URHO3D_PROFILE_STR(profileBlockName.CString(), profileBlockName.Length());
  210. #elif defined(URHO3D_PROFILING)
  211. String profileBlockName("Finish" + resource->GetTypeName());
  212. auto* profiler = owner_->GetSubsystem<Profiler>();
  213. if (profiler)
  214. profiler->BeginBlock(profileBlockName.CString());
  215. #endif
  216. URHO3D_LOGDEBUG("Finishing background loaded resource " + resource->GetName());
  217. success = resource->EndLoad();
  218. #ifdef URHO3D_PROFILING
  219. if (profiler)
  220. profiler->EndBlock();
  221. #endif
  222. }
  223. resource->SetAsyncLoadState(ASYNC_DONE);
  224. if (!success && item.sendEventOnFailure_)
  225. {
  226. using namespace LoadFailed;
  227. VariantMap& eventData = owner_->GetEventDataMap();
  228. eventData[P_RESOURCENAME] = resource->GetName();
  229. owner_->SendEvent(E_LOADFAILED, eventData);
  230. }
  231. // Store to the cache just before sending the event; use same mechanism as for manual resources
  232. if (success || owner_->GetReturnFailedResources())
  233. owner_->AddManualResource(resource);
  234. // Send event, either success or failure
  235. {
  236. using namespace ResourceBackgroundLoaded;
  237. VariantMap& eventData = owner_->GetEventDataMap();
  238. eventData[P_RESOURCENAME] = resource->GetName();
  239. eventData[P_SUCCESS] = success;
  240. eventData[P_RESOURCE] = resource;
  241. owner_->SendEvent(E_RESOURCEBACKGROUNDLOADED, eventData);
  242. }
  243. }
  244. }
  245. #endif