AssetManager.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  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 <AzCore/Asset/AssetManager.h>
  9. #include <AzCore/Asset/AssetSerializer.h>
  10. #include <AzCore/Console/IConsole.h>
  11. #include <AzCore/Interface/Interface.h>
  12. #include <AzCore/IO/SystemFile.h>
  13. #include <AzCore/IO/Streamer/Streamer.h>
  14. #include <AzCore/IO/FileIO.h>
  15. #include <AzCore/IO/GenericStreams.h>
  16. #include <AzCore/Math/Crc.h>
  17. #include <AzCore/Jobs/JobManager.h>
  18. #include <AzCore/Jobs/JobContext.h>
  19. #include <AzCore/Outcome/Outcome.h>
  20. #include <AzCore/Serialization/SerializeContext.h>
  21. #include <AzCore/Serialization/ObjectStream.h>
  22. #include <AzCore/Serialization/Utils.h>
  23. #include <AzCore/std/parallel/thread.h>
  24. #include <AzCore/std/functional.h>
  25. #include <AzCore/std/parallel/condition_variable.h>
  26. #include <AzCore/UnitTest/TestTypes.h>
  27. #include <AZTestShared/Utils/Utils.h>
  28. #include <Streamer/IStreamerMock.h>
  29. #include <Tests/Asset/BaseAssetManagerTest.h>
  30. #include <Tests/Asset/TestAssetTypes.h>
  31. #include <Tests/SerializeContextFixture.h>
  32. #include <Tests/TestCatalog.h>
  33. using namespace AZ;
  34. using namespace AZ::Data;
  35. namespace UnitTest
  36. {
  37. class AssetManagerSystemTest
  38. : public BaseAssetManagerTest
  39. {
  40. public:
  41. // Initialize the Job Manager with 2 threads for the Asset Manager to use.
  42. size_t GetNumJobManagerThreads() const override { return 2; }
  43. };
  44. #if !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_CREATE_DESTROY_TEST
  45. TEST_F(AssetManagerSystemTest, AssetManager_CreateDestroy_TriviallyWorks)
  46. {
  47. // Trvially validate that we can create and destroy an asset manager instance, and that it's only ready while it's created.
  48. // Before creation, IsReady() should be false.
  49. EXPECT_FALSE(AssetManager::IsReady());
  50. AssetManager::Descriptor desc;
  51. AssetManager::Create(desc);
  52. // After creation, the system should be ready and queryable via Instance().
  53. EXPECT_TRUE(AssetManager::IsReady());
  54. AssetManager::Instance();
  55. AssetManager::Destroy();
  56. // After destruction, these should fail again
  57. EXPECT_FALSE(AssetManager::IsReady());
  58. }
  59. #endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_CREATE_DESTROY_TEST
  60. TEST_F(AssetManagerSystemTest, AssetManager_SetInstance_TriviallyWorks)
  61. {
  62. // There shouldn't be an instance yet.
  63. EXPECT_FALSE(AssetManager::IsReady());
  64. // Create an instance and set it.
  65. AssetManager::Descriptor desc;
  66. auto testManager = aznew TestAssetManager(desc);
  67. EXPECT_TRUE(AssetManager::SetInstance(testManager));
  68. // Verify that the instance we get back is the one we created.
  69. auto& goodInstance = AssetManager::Instance();
  70. EXPECT_EQ(&goodInstance, testManager);
  71. AssetManager::Destroy();
  72. }
  73. TEST_F(AssetManagerSystemTest, AssetManager_SetInstance_AssertsWhenAlreadyCreated)
  74. {
  75. // Create an asset manager instance
  76. AssetManager::Descriptor desc;
  77. auto testManager = aznew TestAssetManager(desc);
  78. EXPECT_TRUE(AssetManager::SetInstance(testManager));
  79. // Create a second asset manager instance. This will error because it's connecting two asset managers to the asset manager bus.
  80. AZ_TEST_START_TRACE_SUPPRESSION;
  81. auto testManager2 = aznew TestAssetManager(desc);
  82. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  83. // Try setting the instance without clearing the old one. This will assert.
  84. AZ_TEST_START_TRACE_SUPPRESSION;
  85. EXPECT_TRUE(AssetManager::SetInstance(testManager2));
  86. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  87. // Verify that the instance we get back is the second one we created.
  88. auto& instance = AssetManager::Instance();
  89. EXPECT_EQ(&instance, testManager2);
  90. // Delete the first instance here, since the second SetInstance call caused us to leak the memory.
  91. delete testManager;
  92. // Let the AssetManager clean up the second instance.
  93. AssetManager::Destroy();
  94. }
  95. TEST_F(AssetManagerSystemTest, AssetCallbacks_Clear)
  96. {
  97. // Helper function that always asserts. Used to make sure that clearing asset callbacks actually clears them.
  98. auto testAssetCallbacksClear = []()
  99. {
  100. EXPECT_TRUE(false);
  101. };
  102. AssetBusCallbacks* assetCB1 = aznew AssetBusCallbacks;
  103. // Test clearing the callbacks (using bind allows us to ignore all arguments)
  104. assetCB1->SetCallbacks(
  105. /*OnAssetReady*/ AZStd::bind(testAssetCallbacksClear),
  106. /*OnAssetMoved*/ AZStd::bind(testAssetCallbacksClear),
  107. /*OnAssetReloaded*/ AZStd::bind(testAssetCallbacksClear),
  108. /*OnAssetSaved*/ AZStd::bind(testAssetCallbacksClear),
  109. /*OnAssetUnloaded*/ AZStd::bind(testAssetCallbacksClear),
  110. /*OnAssetError*/ AZStd::bind(testAssetCallbacksClear),
  111. /*OnAssetCanceled*/ AZStd::bind(testAssetCallbacksClear));
  112. assetCB1->ClearCallbacks();
  113. // Invoke all callback functions to make sure nothing is registered anymore.
  114. assetCB1->OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData>());
  115. assetCB1->OnAssetMoved(AZ::Data::Asset<AZ::Data::AssetData>(), nullptr);
  116. assetCB1->OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData>());
  117. assetCB1->OnAssetSaved(AZ::Data::Asset<AZ::Data::AssetData>(), true);
  118. assetCB1->OnAssetUnloaded(AZ::Data::AssetId(), AZ::Data::AssetType());
  119. assetCB1->OnAssetError(AZ::Data::Asset<AZ::Data::AssetData>());
  120. assetCB1->OnAssetCanceled(AZ::Data::AssetId());
  121. delete assetCB1;
  122. }
  123. class AssetManagerShutdownTest
  124. : public BaseAssetManagerTest
  125. {
  126. protected:
  127. static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
  128. static inline const AZ::Uuid MyAsset2Id{ "{BD354AE5-B5D5-402A-A12E-BE3C96F6522B}" };
  129. static inline const AZ::Uuid MyAsset3Id{ "{622C3FC9-5AE2-4E52-AFA2-5F7095ADAB53}" };
  130. static inline const AZ::Uuid MyAsset4Id{ "{EE99215B-7AB4-4757-B8AF-F78BD4903AC4}" };
  131. static inline const AZ::Uuid MyAsset5Id{ "{D9CDAB04-D206-431E-BDC0-1DD615D56197}" };
  132. static inline const AZ::Uuid MyAsset6Id{ "{B2F139C3-5032-4B52-ADCA-D52A8F88E043}" };
  133. DataDrivenHandlerAndCatalog* m_assetHandlerAndCatalog;
  134. bool m_leakExpected = false;
  135. // Initialize the Job Manager with 2 threads for the Asset Manager to use.
  136. size_t GetNumJobManagerThreads() const override { return 2; }
  137. void SetUp() override
  138. {
  139. BaseAssetManagerTest::SetUp();
  140. // create the database
  141. AssetManager::Descriptor desc;
  142. AssetManager::Create(desc);
  143. // create and register an asset handler
  144. m_assetHandlerAndCatalog = aznew DataDrivenHandlerAndCatalog;
  145. m_assetHandlerAndCatalog->m_context = m_serializeContext;
  146. AssetWithCustomData::Reflect(*m_serializeContext);
  147. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset1Id, "MyAsset1.txt");
  148. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset2Id, "MyAsset2.txt");
  149. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset3Id, "MyAsset3.txt");
  150. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset4Id, "MyAsset4.txt");
  151. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset5Id, "MyAsset5.txt");
  152. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset6Id, "MyAsset6.txt");
  153. AZStd::vector<AssetType> types;
  154. m_assetHandlerAndCatalog->GetHandledAssetTypes(types);
  155. for (const auto& type : types)
  156. {
  157. AssetManager::Instance().RegisterHandler(m_assetHandlerAndCatalog, type);
  158. AssetManager::Instance().RegisterCatalog(m_assetHandlerAndCatalog, type);
  159. }
  160. WriteAssetToDisk("MyAsset1.txt", MyAsset1Id.ToString<AZStd::string>().c_str());
  161. WriteAssetToDisk("MyAsset2.txt", MyAsset2Id.ToString<AZStd::string>().c_str());
  162. WriteAssetToDisk("MyAsset3.txt", MyAsset3Id.ToString<AZStd::string>().c_str());
  163. WriteAssetToDisk("MyAsset4.txt", MyAsset4Id.ToString<AZStd::string>().c_str());
  164. WriteAssetToDisk("MyAsset5.txt", MyAsset5Id.ToString<AZStd::string>().c_str());
  165. WriteAssetToDisk("MyAsset6.txt", MyAsset6Id.ToString<AZStd::string>().c_str());
  166. m_leakExpected = false;
  167. }
  168. void TearDown() override
  169. {
  170. // There is no call to AssetManager::Destroy here because it will be called by the various unit tests using this class.
  171. // Also, m_assetHandlerAndCatalog will either get deleted in the unit tests or by AssetManager::Destroy.
  172. if (m_leakExpected)
  173. {
  174. AZ::AllocatorManager::Instance().SetAllocatorLeaking(true);
  175. }
  176. BaseAssetManagerTest::TearDown();
  177. }
  178. void SetLeakExpected()
  179. {
  180. m_leakExpected = true;
  181. }
  182. };
  183. TEST_F(AssetManagerShutdownTest, AssetManagerShutdown_AsyncJobsInQueue_OK)
  184. {
  185. {
  186. Asset<AssetWithCustomData> asset1 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default);
  187. Asset<AssetWithCustomData> asset2 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset2Id, AZ::Data::AssetLoadBehavior::Default);
  188. Asset<AssetWithCustomData> asset3 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset3Id, AZ::Data::AssetLoadBehavior::Default);
  189. Asset<AssetWithCustomData> asset4 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset4Id, AZ::Data::AssetLoadBehavior::Default);
  190. Asset<AssetWithCustomData> asset5 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset5Id, AZ::Data::AssetLoadBehavior::Default);
  191. Asset<AssetWithCustomData> asset6 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset6Id, AZ::Data::AssetLoadBehavior::Default);
  192. }
  193. // destroy asset manager
  194. AssetManager::Destroy();
  195. }
  196. TEST_F(AssetManagerShutdownTest, AssetManagerShutdown_AsyncJobsInQueueWithDelay_OK)
  197. {
  198. {
  199. Asset<AssetWithCustomData> asset1 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default);
  200. Asset<AssetWithCustomData> asset2 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset2Id, AZ::Data::AssetLoadBehavior::Default);
  201. Asset<AssetWithCustomData> asset3 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset3Id, AZ::Data::AssetLoadBehavior::Default);
  202. Asset<AssetWithCustomData> asset4 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset4Id, AZ::Data::AssetLoadBehavior::Default);
  203. Asset<AssetWithCustomData> asset5 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset5Id, AZ::Data::AssetLoadBehavior::Default);
  204. Asset<AssetWithCustomData> asset6 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset6Id, AZ::Data::AssetLoadBehavior::Default);
  205. }
  206. // this should ensure that some jobs are actually running, while some are in queue
  207. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(5));
  208. // destroy asset manager
  209. AssetManager::Destroy();
  210. }
  211. TEST_F(AssetManagerShutdownTest, AssetManagerShutdown_UnregisteringHandler_WhileJobsFlight_OK)
  212. {
  213. {
  214. Asset<AssetWithCustomData> asset1 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default);
  215. Asset<AssetWithCustomData> asset2 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset2Id, AZ::Data::AssetLoadBehavior::Default);
  216. Asset<AssetWithCustomData> asset3 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset3Id, AZ::Data::AssetLoadBehavior::Default);
  217. Asset<AssetWithCustomData> asset4 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset4Id, AZ::Data::AssetLoadBehavior::Default);
  218. Asset<AssetWithCustomData> asset5 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset5Id, AZ::Data::AssetLoadBehavior::Default);
  219. Asset<AssetWithCustomData> asset6 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset6Id, AZ::Data::AssetLoadBehavior::Default);
  220. }
  221. // this should ensure that some jobs are actually running, while some are in queue
  222. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(5));
  223. AssetManager::Instance().PrepareShutDown();
  224. AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
  225. AssetManager::Instance().UnregisterCatalog(m_assetHandlerAndCatalog);
  226. // we have to manually delete the handler since we have already unregistered the handler
  227. delete m_assetHandlerAndCatalog;
  228. // destroy asset manager
  229. AssetManager::Destroy();
  230. }
  231. TEST_F(AssetManagerShutdownTest, AssetManagerShutdown_UnregisteringHandler_WhileJobsFlight_Assert)
  232. {
  233. AssetWithCustomData* assetPtr[6];
  234. {
  235. Asset<AssetWithCustomData> asset1 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default);
  236. Asset<AssetWithCustomData> asset2 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset2Id, AZ::Data::AssetLoadBehavior::Default);
  237. Asset<AssetWithCustomData> asset3 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset3Id, AZ::Data::AssetLoadBehavior::Default);
  238. Asset<AssetWithCustomData> asset4 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset4Id, AZ::Data::AssetLoadBehavior::Default);
  239. Asset<AssetWithCustomData> asset5 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset5Id, AZ::Data::AssetLoadBehavior::Default);
  240. Asset<AssetWithCustomData> asset6 = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset6Id, AZ::Data::AssetLoadBehavior::Default);
  241. // this should ensure that some jobs are actually running, while some are in queue
  242. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(5));
  243. AssetManager::Instance().PrepareShutDown();
  244. // we are unregistering the handler that has still not destroyed all of its active assets
  245. AZ_TEST_START_TRACE_SUPPRESSION;
  246. AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
  247. // unregistering should have caused an error for every one of those 6 assets.
  248. AZ_TEST_STOP_TRACE_SUPPRESSION(6);
  249. AssetManager::Instance().UnregisterCatalog(m_assetHandlerAndCatalog);
  250. AZ_TEST_START_TRACE_SUPPRESSION;
  251. assetPtr[0] = asset1.Get();
  252. assetPtr[1] = asset2.Get();
  253. assetPtr[2] = asset3.Get();
  254. assetPtr[3] = asset4.Get();
  255. assetPtr[4] = asset5.Get();
  256. assetPtr[5] = asset6.Get();
  257. }
  258. AZ_TEST_STOP_TRACE_SUPPRESSION(6); // all the above assets ref count will go to zero here but we have already unregistered the handler
  259. // we have to manually delete the handler since we have already unregistered the handler
  260. // we do not expect asserts here as they will have already notified of problems up above.
  261. delete m_assetHandlerAndCatalog;
  262. // Manually delete the Asset data so we dont have leaks
  263. delete assetPtr[0];
  264. delete assetPtr[1];
  265. delete assetPtr[2];
  266. delete assetPtr[3];
  267. delete assetPtr[4];
  268. delete assetPtr[5];
  269. // destroy asset manager
  270. AssetManager::Destroy();
  271. SetLeakExpected();
  272. }
  273. class MyAssetActiveAssetCountHandler
  274. : public AssetHandler
  275. {
  276. public:
  277. AZ_CLASS_ALLOCATOR(MyAssetActiveAssetCountHandler, AZ::SystemAllocator, 0);
  278. //////////////////////////////////////////////////////////////////////////
  279. // AssetHandler
  280. AssetPtr CreateAsset(const AssetId& id, const AssetType& type) override
  281. {
  282. (void)id;
  283. EXPECT_TRUE(type == AzTypeInfo<AssetWithCustomData>::Uuid());
  284. return aznew AssetWithCustomData(id);
  285. }
  286. Data::AssetHandler::LoadResult LoadAssetData(const Asset<AssetData>& asset, AZStd::shared_ptr<AssetDataStream> stream,
  287. const AssetFilterCB& assetLoadFilterCB) override
  288. {
  289. (void)assetLoadFilterCB;
  290. EXPECT_TRUE(asset.GetType() == AzTypeInfo<AssetWithCustomData>::Uuid());
  291. EXPECT_TRUE(asset.Get() != nullptr && asset.Get()->GetType() == AzTypeInfo<AssetWithCustomData>::Uuid());
  292. size_t assetDataSize = static_cast<size_t>(stream->GetLength());
  293. AssetWithCustomData* myAsset = asset.GetAs<AssetWithCustomData>();
  294. myAsset->m_data = reinterpret_cast<char*>(azmalloc(assetDataSize + 1));
  295. AZStd::string input = AZStd::string::format("Asset<id=%s, type=%s>", asset.GetId().ToString<AZStd::string>().c_str(), asset.GetType().ToString<AZStd::string>().c_str());
  296. stream->Read(assetDataSize, myAsset->m_data);
  297. myAsset->m_data[assetDataSize] = 0;
  298. return (azstricmp(input.c_str(), myAsset->m_data) == 0) ?
  299. Data::AssetHandler::LoadResult::LoadComplete :
  300. Data::AssetHandler::LoadResult::Error;
  301. }
  302. bool SaveAssetData(const Asset<AssetData>& asset, IO::GenericStream* stream) override
  303. {
  304. EXPECT_TRUE(asset.GetType() == AzTypeInfo<AssetWithCustomData>::Uuid());
  305. AZStd::string output = AZStd::string::format("Asset<id=%s, type=%s>", asset.GetId().ToString<AZStd::string>().c_str(), asset.GetType().ToString<AZStd::string>().c_str());
  306. stream->Write(output.size(), output.c_str());
  307. return true;
  308. }
  309. void DestroyAsset(AssetPtr ptr) override
  310. {
  311. EXPECT_TRUE(ptr->GetType() == AzTypeInfo<AssetWithCustomData>::Uuid());
  312. delete ptr;
  313. }
  314. void GetHandledAssetTypes(AZStd::vector<AssetType>& assetTypes) override
  315. {
  316. assetTypes.push_back(AzTypeInfo<AssetWithCustomData>::Uuid());
  317. }
  318. };
  319. struct MyAssetHolder
  320. {
  321. AZ_TYPE_INFO(MyAssetHolder, "{1DA71115-547B-4f32-B230-F3C70608AD68}");
  322. AZ_CLASS_ALLOCATOR(MyAssetHolder, AZ::SystemAllocator, 0);
  323. Asset<AssetWithCustomData> m_asset1;
  324. Asset<AssetWithCustomData> m_asset2;
  325. };
  326. class AssetManagerTest
  327. : public BaseAssetManagerTest
  328. {
  329. protected:
  330. DataDrivenHandlerAndCatalog* m_assetHandlerAndCatalog;
  331. TestAssetManager* m_testAssetManager;
  332. public:
  333. static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
  334. static inline const AZ::Uuid MyAsset2Id{ "{BD354AE5-B5D5-402A-A12E-BE3C96F6522B}" };
  335. static inline const AZ::Uuid MyAsset3Id{ "{8398759D-5D84-4E71-B9E2-69F3C0822A30}" };
  336. // Initialize the Job Manager with a single thread for the Asset Manager to use.
  337. size_t GetNumJobManagerThreads() const override { return 1; }
  338. void SetUp() override
  339. {
  340. BaseAssetManagerTest::SetUp();
  341. // create the database
  342. AssetManager::Descriptor desc;
  343. m_testAssetManager = aznew TestAssetManager(desc);
  344. AssetManager::SetInstance(m_testAssetManager);
  345. // create and register an asset handler
  346. m_assetHandlerAndCatalog = aznew DataDrivenHandlerAndCatalog;
  347. m_assetHandlerAndCatalog->m_context = m_serializeContext;
  348. AssetWithCustomData::Reflect(*m_serializeContext);
  349. EmptyAssetWithInstanceCount::Reflect(*m_serializeContext);
  350. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset1Id, "MyAsset1.txt");
  351. m_assetHandlerAndCatalog->AddAsset<AssetWithCustomData>(MyAsset2Id, "MyAsset2.txt");
  352. m_assetHandlerAndCatalog->AddAsset<EmptyAssetWithInstanceCount>(MyAsset3Id, "MyAsset3.txt");
  353. AZStd::vector<AssetType> types;
  354. m_assetHandlerAndCatalog->GetHandledAssetTypes(types);
  355. for (const auto& type : types)
  356. {
  357. AssetManager::Instance().RegisterHandler(m_assetHandlerAndCatalog, type);
  358. AssetManager::Instance().RegisterCatalog(m_assetHandlerAndCatalog, type);
  359. }
  360. WriteAssetToDisk("MyAsset1.txt", MyAsset1Id.ToString<AZStd::string>().c_str());
  361. WriteAssetToDisk("MyAsset2.txt", MyAsset2Id.ToString<AZStd::string>().c_str());
  362. }
  363. void TearDown() override
  364. {
  365. // Manually release the handler
  366. AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
  367. AssetManager::Instance().UnregisterCatalog(m_assetHandlerAndCatalog);
  368. delete m_assetHandlerAndCatalog;
  369. // destroy the database
  370. AssetManager::Destroy();
  371. BaseAssetManagerTest::TearDown();
  372. }
  373. void OnLoadedClassReady(void* classPtr, const Uuid& /*classId*/)
  374. {
  375. MyAssetHolder* assetHolder = reinterpret_cast<MyAssetHolder*>(classPtr);
  376. EXPECT_TRUE(assetHolder->m_asset1 && assetHolder->m_asset2);
  377. delete assetHolder;
  378. }
  379. void SaveObjects(ObjectStream* writer, MyAssetHolder* assetHolder)
  380. {
  381. bool success = true;
  382. success = writer->WriteClass(assetHolder);
  383. EXPECT_TRUE(success);
  384. }
  385. void OnDone(ObjectStream::Handle handle, bool success, bool* done)
  386. {
  387. (void)handle;
  388. EXPECT_TRUE(success);
  389. *done = true;
  390. }
  391. };
  392. // this test makes sure that saving and loading asset data remains symmetrical and does not lose fields.
  393. void Test_AssetSerialization(AssetId idToUse, AZ::DataStream::StreamType typeToUse)
  394. {
  395. SerializeContext context;
  396. AssetWithAssetReference::Reflect(context);
  397. AssetWithAssetReference myRef;
  398. AssetWithAssetReference myRefEmpty; // we always test with an empty (Default) ref too.
  399. AssetWithAssetReference myRef2; // to be read into
  400. // Create an asset with a fake asset reference, and set it not to load because it's fake
  401. {
  402. Asset<AssetWithCustomData> assetRef;
  403. ASSERT_TRUE(assetRef.Create(idToUse, AZ::Data::AssetLoadBehavior::NoLoad, false));
  404. myRef.m_asset = assetRef;
  405. EXPECT_EQ(myRef.m_asset.GetType(), azrtti_typeid<AssetWithCustomData>());
  406. }
  407. // Set the load behavior on the empty ref to Default instead of NoLoad.
  408. myRefEmpty.m_asset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::Default);
  409. char memBuffer[4096];
  410. {
  411. // we are scoping the memory stream to avoid detritus staying behind in it.
  412. // let's not be nice about this. Put garbage in the buffer so that it doesn't get away with
  413. // not checking the length of the incoming stream.
  414. memset(memBuffer, '<', AZ_ARRAY_SIZE(memBuffer));
  415. AZ::IO::MemoryStream memStream(memBuffer, AZ_ARRAY_SIZE(memBuffer), 0);
  416. ASSERT_TRUE(Utils::SaveObjectToStream(memStream, typeToUse, &myRef, &context));
  417. EXPECT_GT(memStream.GetLength(), 0); // something should have been written.
  418. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  419. EXPECT_TRUE(Utils::LoadObjectFromStreamInPlace(memStream, myRef2, &context));
  420. EXPECT_EQ(myRef2.m_asset.GetType(), azrtti_typeid<AssetWithCustomData>());
  421. EXPECT_EQ(myRef2.m_asset.GetId(), idToUse);
  422. EXPECT_EQ(myRef2.m_asset.GetAutoLoadBehavior(), myRef.m_asset.GetAutoLoadBehavior());
  423. }
  424. {
  425. memset(memBuffer, '<', AZ_ARRAY_SIZE(memBuffer));
  426. AZ::IO::MemoryStream memStream(memBuffer, AZ_ARRAY_SIZE(memBuffer), 0);
  427. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  428. ASSERT_TRUE(Utils::SaveObjectToStream(memStream, typeToUse, &myRefEmpty, &context));
  429. EXPECT_GT(memStream.GetLength(), 0); // something should have been written.
  430. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  431. ASSERT_TRUE(Utils::LoadObjectFromStreamInPlace(memStream, myRef2, &context));
  432. EXPECT_EQ(myRef2.m_asset.GetType(), azrtti_typeid<AssetData>());
  433. EXPECT_EQ(myRef2.m_asset.GetId(), myRefEmpty.m_asset.GetId());
  434. EXPECT_EQ(myRef2.m_asset.GetAutoLoadBehavior(), myRefEmpty.m_asset.GetAutoLoadBehavior());
  435. }
  436. }
  437. TEST_F(AssetManagerTest, AssetSerializerTest)
  438. {
  439. auto assets = {
  440. AssetId("{3E971FD2-DB5F-4617-9061-CCD3606124D0}", 0x707a11ed),
  441. AssetId("{A482C6F3-9943-4C19-8970-974EFF6F1389}", 0x00000000),
  442. };
  443. for (int streamTypeIndex = 0; streamTypeIndex < static_cast<int>(AZ::DataStream::ST_MAX); ++streamTypeIndex)
  444. {
  445. for (auto asset : assets)
  446. {
  447. Test_AssetSerialization(asset, static_cast<AZ::DataStream::StreamType>(streamTypeIndex));
  448. }
  449. }
  450. }
  451. // Test for serialize class data which contains a reference asset which handler wasn't registered to AssetManager
  452. TEST_F(AssetManagerTest, AssetSerializerAssetReferenceTest)
  453. {
  454. auto assetId = AssetId("{3E971FD2-DB5F-4617-9061-CCD3606124D0}", 0);
  455. SerializeContext context;
  456. AssetWithAssetReference::Reflect(context);
  457. char memBuffer[4096];
  458. AZ::IO::MemoryStream memStream(memBuffer, AZ_ARRAY_SIZE(memBuffer), 0);
  459. // generate the data stream for the object contains a reference of asset
  460. AssetWithAssetReference toSave;
  461. {
  462. Asset<AssetWithCustomData> assetRef;
  463. ASSERT_TRUE(assetRef.Create(assetId, AZ::Data::AssetLoadBehavior::PreLoad, false));
  464. toSave.m_asset = assetRef;
  465. }
  466. Utils::SaveObjectToStream(memStream, DataStream::StreamType::ST_BINARY, &toSave, &context);
  467. toSave.m_asset.Release();
  468. // Unregister asset handler for AssetWithCustomData
  469. AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
  470. Utils::FilterDescriptor desc;
  471. AssetWithAssetReference toRead;
  472. // return true if loading with none filter or ignore unknown classes filter
  473. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  474. AZ_TEST_START_TRACE_SUPPRESSION;
  475. desc.m_flags = 0;
  476. ASSERT_TRUE(Utils::LoadObjectFromStreamInPlace(memStream, toRead, &context, desc));
  477. // LoadObjectFromStreamInPlace generates two errors. One is can't find the handler. Another one is can't load referenced asset.
  478. AZ_TEST_STOP_TRACE_SUPPRESSION(2);
  479. // return true if loading with ignore unknown classes filter
  480. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  481. AZ_TEST_START_TRACE_SUPPRESSION;
  482. desc.m_flags = ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES;
  483. ASSERT_TRUE(Utils::LoadObjectFromStreamInPlace(memStream, toRead, &context, desc));
  484. // LoadObjectFromStreamInPlace generates two errors. One is can't find the handler. Another one is can't load referenced asset.
  485. AZ_TEST_STOP_TRACE_SUPPRESSION(2);
  486. // return false if loading with strict filter
  487. memStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  488. AZ_TEST_START_TRACE_SUPPRESSION;
  489. desc.m_flags = ObjectStream::FILTERFLAG_STRICT;
  490. ASSERT_FALSE(Utils::LoadObjectFromStreamInPlace(memStream, toRead, &context, desc));
  491. // LoadObjectFromStreamInPlace generates two errors. One is can't find the handler. Another one is can't load referenced asset.
  492. AZ_TEST_STOP_TRACE_SUPPRESSION(2);
  493. }
  494. TEST_F(AssetManagerTest, AssetCanBeReleased)
  495. {
  496. const auto assetId = AssetId(Uuid::CreateRandom());
  497. Asset<EmptyAssetWithInstanceCount> asset = m_testAssetManager->CreateAsset<EmptyAssetWithInstanceCount>(assetId);
  498. EXPECT_NE(asset.Get(), nullptr);
  499. asset.Release();
  500. EXPECT_EQ(asset.Get(), nullptr);
  501. EXPECT_EQ(asset.GetId(), assetId);
  502. EXPECT_EQ(asset.GetType(), AzTypeInfo<EmptyAssetWithInstanceCount>::Uuid());
  503. }
  504. TEST_F(AssetManagerTest, AssetCanBeReset)
  505. {
  506. const auto assetId = AssetId(Uuid::CreateRandom());
  507. Asset<EmptyAssetWithInstanceCount> asset = m_testAssetManager->CreateAsset<EmptyAssetWithInstanceCount>(assetId);
  508. EXPECT_NE(asset.Get(), nullptr);
  509. asset.Reset();
  510. EXPECT_EQ(asset.Get(), nullptr);
  511. EXPECT_FALSE(asset.GetId().IsValid());
  512. EXPECT_TRUE(asset.GetType().IsNull());
  513. }
  514. TEST_F(AssetManagerTest, AssetPtrRefCount)
  515. {
  516. // Asset ptr tests.
  517. Asset<EmptyAssetWithInstanceCount> someAsset = AssetManager::Instance().CreateAsset<EmptyAssetWithInstanceCount>(Uuid::CreateRandom(), AZ::Data::AssetLoadBehavior::Default);
  518. EmptyAssetWithInstanceCount* someData = someAsset.Get();
  519. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 1);
  520. // Construct with flags
  521. {
  522. Asset<EmptyAssetWithInstanceCount> assetWithFlags(AssetLoadBehavior::PreLoad);
  523. EXPECT_TRUE(!assetWithFlags.GetId().IsValid());
  524. EXPECT_EQ(assetWithFlags.GetType(), AzTypeInfo<EmptyAssetWithInstanceCount>::Uuid());
  525. EXPECT_EQ(assetWithFlags.GetAutoLoadBehavior(), AssetLoadBehavior::PreLoad);
  526. }
  527. // Construct w/ data (verify id & type)
  528. {
  529. Asset<EmptyAssetWithInstanceCount> assetWithData(someData, AZ::Data::AssetLoadBehavior::Default);
  530. EXPECT_EQ(someData->GetUseCount(), 2);
  531. EXPECT_TRUE(assetWithData.GetId().IsValid());
  532. EXPECT_EQ(assetWithData.GetType(), AzTypeInfo<EmptyAssetWithInstanceCount>::Uuid());
  533. EXPECT_EQ(assetWithData.GetAutoLoadBehavior(), AssetLoadBehavior::Default);
  534. }
  535. // Copy construct (verify id & type, acquisition of new data)
  536. {
  537. Asset<EmptyAssetWithInstanceCount> assetWithData = AssetManager::Instance().CreateAsset<EmptyAssetWithInstanceCount>(Uuid::CreateRandom(), AZ::Data::AssetLoadBehavior::Default);
  538. EmptyAssetWithInstanceCount* newData = assetWithData.Get();
  539. Asset<EmptyAssetWithInstanceCount> assetWithData2(assetWithData);
  540. // Underlying data should be used twice through a reference in both assets.
  541. EXPECT_EQ(assetWithData->GetUseCount(), 2);
  542. EXPECT_EQ(assetWithData.Get(), newData);
  543. EXPECT_EQ(assetWithData.Get(), assetWithData2.Get());
  544. // Every other value should also be copied between the two assets
  545. EXPECT_EQ(assetWithData.GetId(), assetWithData2.GetId());
  546. EXPECT_EQ(assetWithData.GetType(), assetWithData2.GetType());
  547. EXPECT_EQ(assetWithData.GetAutoLoadBehavior(), assetWithData2.GetAutoLoadBehavior());
  548. }
  549. // Allow the asset manager to purge assets on the dead list.
  550. AssetManager::Instance().DispatchEvents();
  551. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 1);
  552. // Move construct (verify id & type, release of old data, acquisition of new)
  553. {
  554. Asset<EmptyAssetWithInstanceCount> assetWithData = AssetManager::Instance().CreateAsset<EmptyAssetWithInstanceCount>(Uuid::CreateRandom(), AZ::Data::AssetLoadBehavior::Default);
  555. EmptyAssetWithInstanceCount* origData = assetWithData.Get();
  556. AssetId origId = assetWithData.GetId();
  557. AssetType origType = assetWithData.GetType();
  558. Asset<EmptyAssetWithInstanceCount> assetWithData2(AZStd::move(assetWithData));
  559. // The original asset should only have default values now
  560. EXPECT_EQ(assetWithData.Get(), nullptr);
  561. EXPECT_EQ(assetWithData.GetId(), AssetId());
  562. EXPECT_EQ(assetWithData.GetType(), AssetType::CreateNull());
  563. EXPECT_EQ(assetWithData.GetAutoLoadBehavior(), AssetLoadBehavior::Default);
  564. // The new asset should have all of the original asset's values
  565. EXPECT_EQ(assetWithData2.Get(), origData);
  566. EXPECT_EQ(assetWithData2.GetId(), origId);
  567. EXPECT_EQ(assetWithData2.GetType(), origType);
  568. EXPECT_EQ(assetWithData2.GetAutoLoadBehavior(), assetWithData.GetAutoLoadBehavior());
  569. // The underlying data should only have one reference, which is the new asset.
  570. EXPECT_EQ(origData->GetUseCount(), 1);
  571. }
  572. // Allow the asset manager to purge assets on the dead list.
  573. AssetManager::Instance().DispatchEvents();
  574. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 1);
  575. // Copy from r-value (verify id & type, release of old data, acquisition of new)
  576. {
  577. Asset<EmptyAssetWithInstanceCount> assetWithData = AssetManager::Instance().CreateAsset<EmptyAssetWithInstanceCount>(Uuid::CreateRandom(), AZ::Data::AssetLoadBehavior::Default);
  578. EmptyAssetWithInstanceCount* newData = assetWithData.Get();
  579. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 2);
  580. Asset<EmptyAssetWithInstanceCount> assetWithData2 = AssetManager::Instance().CreateAsset<EmptyAssetWithInstanceCount>(Uuid::CreateRandom(), AZ::Data::AssetLoadBehavior::Default);
  581. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 3);
  582. assetWithData2 = AZStd::move(assetWithData);
  583. // Allow the asset manager to purge assets on the dead list.
  584. AssetManager::Instance().DispatchEvents();
  585. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 2);
  586. EXPECT_EQ(assetWithData.Get(), nullptr);
  587. EXPECT_EQ(assetWithData2.Get(), newData);
  588. EXPECT_EQ(newData->GetUseCount(), 1);
  589. }
  590. // Allow the asset manager to purge assets on the dead list.
  591. AssetManager::Instance().DispatchEvents();
  592. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 1);
  593. {
  594. // Test copy of a different, but compatible asset type to make sure it takes on the correct info.
  595. Asset<AssetData> genericAsset(someData, AZ::Data::AssetLoadBehavior::Default);
  596. EXPECT_TRUE(genericAsset.GetId().IsValid());
  597. EXPECT_EQ(genericAsset.GetType(), AzTypeInfo<EmptyAssetWithInstanceCount>::Uuid());
  598. // Test copy of a different incompatible asset type to make sure error is caught, and no data is populated.
  599. AZ_TEST_START_TRACE_SUPPRESSION;
  600. Asset<AssetWithCustomData> incompatibleAsset(someData, AZ::Data::AssetLoadBehavior::Default);
  601. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  602. EXPECT_EQ(incompatibleAsset.Get(), nullptr); // Verify data assignment was rejected
  603. EXPECT_TRUE(!incompatibleAsset.GetId().IsValid()); // Verify asset Id was not assigned
  604. EXPECT_EQ(AzTypeInfo<AssetWithCustomData>::Uuid(), incompatibleAsset.GetType()); // Verify asset ptr type is still the original template type.
  605. }
  606. // Allow the asset manager to purge assets on the dead list.
  607. AssetManager::Instance().DispatchEvents();
  608. EXPECT_EQ(EmptyAssetWithInstanceCount::s_instanceCount, 1);
  609. EXPECT_EQ(someData->GetUseCount(), 1);
  610. AssetManager::Instance().DispatchEvents();
  611. }
  612. TEST_F(AssetManagerTest, AssignAssetData_NoExistingAsset_DoesNotCrash)
  613. {
  614. Asset<AssetWithCustomData> someAsset(new AssetWithCustomData(Uuid::CreateRandom(), AZ::Data::AssetData::AssetStatus::Ready),
  615. AZ::Data::AssetLoadBehavior::Default); // the data is now "owned" by this, it's not a copy
  616. AssetManager::Instance().AssignAssetData(someAsset);
  617. }
  618. TEST_F(AssetManagerTest, AssetManager_UnregisterHandler_OnlyErrorsForAssetsCreatedByAssetManager)
  619. {
  620. // Unregister fixture handler(MyAssetHandlerAndCatalog) until the end of the test
  621. AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
  622. MyAssetActiveAssetCountHandler testHandler;
  623. AssetManager::Instance().RegisterHandler(&testHandler, AzTypeInfo<AssetWithCustomData>::Uuid());
  624. {
  625. // Unmanaged asset created in Scope #1
  626. Asset<AssetWithCustomData> nonAssetManagerManagedAsset(aznew AssetWithCustomData(), AZ::Data::AssetLoadBehavior::Default);
  627. {
  628. // Managed asset created in Scope #2
  629. Asset<AssetWithCustomData> assetManagerManagedAsset1;
  630. assetManagerManagedAsset1.Create(MyAsset1Id);
  631. Asset<AssetWithCustomData> assetManagerManagedAsset2 = AssetManager::Instance().CreateAsset(MyAsset2Id, azrtti_typeid<AssetWithCustomData>(), AZ::Data::AssetLoadBehavior::Default);
  632. // There are still two assets handled by the AssetManager so it should error once
  633. // An assert will occur if the AssetHandler is unregistered and there are still active assets
  634. // We expect TWO assertions here since there will be TWO entries in the map
  635. // one from Create and one from GetAsset.
  636. AZ_TEST_START_TRACE_SUPPRESSION;
  637. AssetManager::Instance().UnregisterHandler(&testHandler);
  638. AZ_TEST_STOP_TRACE_SUPPRESSION(2);
  639. // Re-register AssetHandler and let the managed assets ref count hit zero which will remove them from the AssetManager
  640. AssetManager::Instance().RegisterHandler(&testHandler, AzTypeInfo<AssetWithCustomData>::Uuid());
  641. }
  642. // Unregistering the AssetHandler now should result in 0 error messages since the m_assetHandlerAndCatalog::m_nActiveAsset count should be 0.
  643. AZ_TEST_START_TRACE_SUPPRESSION;
  644. AssetManager::Instance().UnregisterHandler(&testHandler);
  645. AZ_TEST_STOP_TRACE_SUPPRESSION(0);
  646. //Re-register AssetHandler and let the block scope end for the non managed asset.
  647. AssetManager::Instance().RegisterHandler(&testHandler, AzTypeInfo<AssetWithCustomData>::Uuid());
  648. }
  649. // Unregister the TestAssetHandler one last time. The unmanaged asset has already been destroyed.
  650. // The m_assetHandlerAndCatalog::m_nActiveAsset count should still be 0 as the it did not manage the nonAssetManagerManagedAsset object
  651. AZ_TEST_START_TRACE_SUPPRESSION;
  652. AssetManager::Instance().UnregisterHandler(&testHandler);
  653. AZ_TEST_STOP_TRACE_SUPPRESSION(0);
  654. // Re-register the fixture handler so that the UnitTest fixture is able to cleanup the AssetManager without errors
  655. AssetManager::Instance().RegisterHandler(m_assetHandlerAndCatalog, AzTypeInfo<AssetWithCustomData>::Uuid());
  656. AssetManager::Instance().RegisterHandler(m_assetHandlerAndCatalog, AzTypeInfo<EmptyAssetWithInstanceCount>::Uuid());
  657. }
  658. TEST_F(AssetManagerTest, AssetManager_SuspendResumeAssetRelease)
  659. {
  660. auto asset = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AssetLoadBehavior::Default);
  661. asset.BlockUntilLoadComplete();
  662. AssetManager::Instance().DispatchEvents();
  663. AssetManager::Instance().SuspendAssetRelease();
  664. asset = {};
  665. AssetManager::Instance().DispatchEvents();
  666. auto&& assets = m_testAssetManager->GetAssets();
  667. EXPECT_EQ(assets.size(), 1);
  668. EXPECT_NE(assets.find(MyAsset1Id), assets.end());
  669. AssetManager::Instance().ResumeAssetRelease();
  670. // Sleep to allow for the assets to release
  671. int retryCount = 100;
  672. while ((--retryCount>0) && assets.size() > 0)
  673. {
  674. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
  675. }
  676. EXPECT_EQ(assets.size(), 0);
  677. }
  678. TEST_F(AssetManagerTest, AssetManager_SuspendResumeAssetRelease_ReusedAssetIsNotReleased)
  679. {
  680. auto asset = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AssetLoadBehavior::Default);
  681. asset.BlockUntilLoadComplete();
  682. AssetManager::Instance().SuspendAssetRelease();
  683. asset = {};
  684. AssetManager::Instance().DispatchEvents();
  685. asset = AssetManager::Instance().GetAsset<AssetWithCustomData>(MyAsset1Id, AssetLoadBehavior::Default);
  686. auto&& assets = m_testAssetManager->GetAssets();
  687. AssetManager::Instance().ResumeAssetRelease();
  688. EXPECT_EQ(assets.size(), 1);
  689. EXPECT_NE(assets.find(MyAsset1Id), assets.end());
  690. }
  691. }