AssetCollectionAsyncLoader.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/Utils/AssetCollectionAsyncLoader.h>
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Component/TickBus.h>
  11. #include <AzCore/std/algorithm.h>
  12. namespace AZ
  13. {
  14. //! This is the Job class that runs until all assetPaths become valid AssetIds.
  15. //! A valid AssetId doesn't mean that the asset is ready and loaded in memory,
  16. //! it simply means the assetPath is acknowledged by the AP as an asset in the database
  17. //! that CAN be loaded.
  18. class AssetDiscoveryJob : public Job
  19. {
  20. public:
  21. AZ_CLASS_ALLOCATOR(AssetDiscoveryJob, ThreadPoolAllocator);
  22. //! @queryWaitMilliseconds How long to wait between each attempt to query for assets that are not yet present in the asset database.
  23. AssetDiscoveryJob(AssetCollectionAsyncLoader& assetCollectionLoadManager,
  24. const AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList,
  25. JobContext* context = nullptr, uint32_t queryWaitMilliseconds = 1000)
  26. : Job(false, context)
  27. , m_assetCollectionLoadManager(assetCollectionLoadManager)
  28. , m_assetList(assetList)
  29. , m_queryWaitMilliseconds(queryWaitMilliseconds)
  30. , m_isRunning(false)
  31. {
  32. }
  33. void Process() override
  34. {
  35. m_isRunning.store(true);
  36. while (!IsCancelled())
  37. {
  38. //Remove assets from m_assetList as they appear in the asset database.
  39. m_assetList.erase(AZStd::remove_if(m_assetList.begin(), m_assetList.end(),
  40. [&](const AssetCollectionAsyncLoader::AssetToLoadInfo& assetToLoadInfo)
  41. {
  42. const auto& assetPath = assetToLoadInfo.m_assetPath;
  43. const auto& assetType = assetToLoadInfo.m_assetType;
  44. Data::AssetId assetId;
  45. Data::AssetCatalogRequestBus::BroadcastResult(
  46. assetId, &Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  47. assetPath.c_str(), assetType, false);
  48. if (assetId.IsValid())
  49. {
  50. // Notify the AssetCollectionAsyncLoader of such wonderful news.
  51. m_assetCollectionLoadManager.OnAssetIsValid(assetPath, assetId, assetType);
  52. return true;
  53. }
  54. return false;
  55. }),
  56. m_assetList.end());
  57. // If the asset list is empty this job is done.
  58. if (m_assetList.empty())
  59. {
  60. break;
  61. }
  62. //time to sleep.
  63. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_queryWaitMilliseconds));
  64. }
  65. m_isRunning.store(false);
  66. }
  67. bool IsRunning() const
  68. {
  69. return m_isRunning.load();
  70. }
  71. private:
  72. AssetCollectionAsyncLoader& m_assetCollectionLoadManager;
  73. AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo> m_assetList;
  74. uint32_t m_queryWaitMilliseconds;
  75. AZStd::atomic_bool m_isRunning;
  76. }; // class AssetDiscoveryJob
  77. bool AssetCollectionAsyncLoader::LoadAssetsAsync(const AZStd::vector<AssetToLoadInfo>& assetList, OnAssetReadyCallback onAssetReadyCB)
  78. {
  79. if (assetList.empty())
  80. {
  81. return false;
  82. }
  83. {
  84. if (!!m_assetDiscoveryJob && (static_cast<AssetDiscoveryJob*>(m_assetDiscoveryJob.get()))->IsRunning())
  85. {
  86. AZ_Error(AssetCollectionAsyncLoaderName, false, "Pending requests must be cancelled before calling LoadAssetsAsync");
  87. return false;
  88. }
  89. AZStd::shared_lock<decltype(m_mutex)> lock(m_mutex);
  90. if (!m_assetsToLoad.empty())
  91. {
  92. AZ_Error(AssetCollectionAsyncLoaderName, false, "Some assets were still pending for loading, call Cancel before calling LoadAssetsAsync");
  93. return false;
  94. }
  95. }
  96. // Let's clear and reset to start with a clean slate.
  97. Cancel();
  98. AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
  99. m_onAssetReadyCB = onAssetReadyCB;
  100. m_assetsToLoad.reserve(assetList.size());
  101. for (const auto& assetToLoadInfo : assetList)
  102. {
  103. const auto& assetPath = assetToLoadInfo.m_assetPath;
  104. AZ_Warning(AssetCollectionAsyncLoaderName, !m_assetsToLoad.count(assetPath), "Asset with path %s was already scheduled for loading", assetPath.c_str());
  105. m_assetsToLoad.insert(assetPath);
  106. }
  107. // Prepare to create a cancellable job.
  108. AZ::JobManagerDesc desc;
  109. desc.m_jobManagerName = "AssetCollectionAsyncLoader";
  110. AZ::JobManagerThreadDesc threadDesc;
  111. desc.m_workerThreads.push_back(threadDesc);
  112. m_jobManager = AZStd::make_unique<AZ::JobManager>(desc);
  113. m_jobCancelGroup = AZStd::make_unique<AZ::JobCancelGroup>();
  114. m_jobContext = AZStd::make_unique<AZ::JobContext>(*m_jobManager, *m_jobCancelGroup);
  115. m_jobUsedForCancellation = AZStd::make_unique<AZ::JobCompletion>(m_jobContext.get());
  116. m_jobUsedForCancellation->Reset(true);
  117. //Kick off the job.
  118. m_assetDiscoveryJob = AZStd::make_unique<AssetDiscoveryJob>(*this, assetList, m_jobContext.get());
  119. m_assetDiscoveryJob->SetDependent(m_jobUsedForCancellation.get());
  120. m_assetDiscoveryJob->Start();
  121. return true;
  122. }
  123. void AssetCollectionAsyncLoader::Cancel()
  124. {
  125. if (!m_assetDiscoveryJob)
  126. {
  127. return;
  128. }
  129. m_jobCancelGroup->Cancel();
  130. m_jobUsedForCancellation->StartAndWaitForCompletion();
  131. m_jobCancelGroup->Reset();
  132. m_assetDiscoveryJob = nullptr;
  133. m_jobUsedForCancellation = nullptr;
  134. m_jobContext = nullptr;
  135. m_jobCancelGroup = nullptr;
  136. m_jobManager = nullptr;
  137. AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
  138. ResetLocked();
  139. }
  140. void AssetCollectionAsyncLoader::ResetLocked()
  141. {
  142. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  143. m_onAssetReadyCB = nullptr;
  144. m_assetsToLoad.clear();
  145. m_assetsToNotify.clear();
  146. m_assetIdStrToAssetPath.clear();
  147. m_readyAssets.clear();
  148. m_notReadyAssets.clear();
  149. }
  150. void AssetCollectionAsyncLoader::PostNotifyReadyAssetsCB(Data::Asset<Data::AssetData> asset, bool success)
  151. {
  152. AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
  153. AZStd::string assetIdStr = asset.GetId().ToString<AZStd::string>();
  154. // Find the asset in private list.
  155. {
  156. AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
  157. const auto findPathIt = m_assetIdStrToAssetPath.find(assetIdStr);
  158. AZ_Assert(findPathIt != m_assetIdStrToAssetPath.end(), "Got an update for asset %s but it doesn't belong to this load manager\n", asset.GetHint().c_str());
  159. AZStd::string assetPath = findPathIt->second;
  160. AZ_Assert(m_assetsToLoad.count(assetPath), "Asset with path %s, hint %s was not scheduled to load\n", assetPath.c_str(), asset.GetHint().c_str());
  161. m_notReadyAssets.erase(assetPath);
  162. m_assetsToLoad.erase(assetPath);
  163. m_readyAssets[assetPath] = asset;
  164. m_assetsToNotify[assetPath] = success;
  165. }
  166. // Notify the caller on main thread.
  167. AZ::TickBus::QueueFunction([&]()
  168. {
  169. size_t pendingAssetsCount = 0;
  170. decltype(m_assetsToNotify) assetList;
  171. {
  172. AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
  173. assetList.swap(m_assetsToNotify);
  174. pendingAssetsCount = m_assetsToLoad.size();
  175. }
  176. for (const auto& [assetPath, assetSuccess] : assetList)
  177. {
  178. m_onAssetReadyCB(assetPath, assetSuccess, pendingAssetsCount);
  179. }
  180. });
  181. }
  182. void AssetCollectionAsyncLoader::OnAssetIsValid(AZStd::string_view assetPath, const Data::AssetId& assetId, const Data::AssetType& assetType)
  183. {
  184. // Kick off asset loading.
  185. auto asset = Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::QueueLoad);
  186. const auto assetIdStr = assetId.ToString<AZStd::string>();
  187. {
  188. AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
  189. m_assetIdStrToAssetPath[assetIdStr] = assetPath;
  190. m_notReadyAssets[assetPath] = asset;
  191. }
  192. Data::AssetBus::MultiHandler::BusConnect(assetId);
  193. }
  194. ///////////////////////////////////////////////////////////////////////
  195. // AZ::Data::AssetBus::Handler overrides
  196. void AssetCollectionAsyncLoader::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
  197. {
  198. PostNotifyReadyAssetsCB(asset, true /*success*/);
  199. }
  200. void AssetCollectionAsyncLoader::OnAssetError(Data::Asset<Data::AssetData> asset)
  201. {
  202. PostNotifyReadyAssetsCB(asset, false /*success*/);
  203. }
  204. }