InstanceDatabase.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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 <AtomCore/Instance/InstanceDatabase.h>
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Memory/PoolAllocator.h>
  11. #include <AzCore/std/parallel/conditional_variable.h>
  12. #include <AzCore/UnitTest/TestTypes.h>
  13. #include <AzCore/Debug/Timer.h>
  14. using namespace AZ;
  15. using namespace AZ::Data;
  16. namespace UnitTest
  17. {
  18. static const AssetId s_assetId0{ Uuid("{5B29FE2B-6B41-48C9-826A-C723951B0560}") };
  19. static const AssetId s_assetId1{ Uuid("{BD354AE5-B5D5-402A-A12E-BE3C96F6522B}") };
  20. static const AssetId s_assetId2{ Uuid("{EE99215B-7AB4-4757-B8AF-F78BD4903AC4}") };
  21. static const AssetId s_assetId3{ Uuid("{D9CDAB04-D206-431E-BDC0-1DD615D56197}") };
  22. static const InstanceId s_instanceId0{ InstanceId::CreateFromAssetId(s_assetId0) };
  23. static const InstanceId s_instanceId1{ InstanceId::CreateFromAssetId(s_assetId1) };
  24. static const InstanceId s_instanceId2{ InstanceId::CreateFromAssetId(s_assetId2) };
  25. static const InstanceId s_instanceId3{ InstanceId::CreateFromAssetId(s_assetId3) };
  26. // test asset type
  27. class TestAssetType : public AssetData
  28. {
  29. public:
  30. AZ_CLASS_ALLOCATOR(TestAssetType, AZ::SystemAllocator);
  31. AZ_RTTI(TestAssetType, "{73D60606-BDE5-44F9-9420-5649FE7BA5B8}", AssetData);
  32. TestAssetType()
  33. {
  34. m_status = AssetStatus::Ready;
  35. }
  36. };
  37. class TestInstanceA : public InstanceData
  38. {
  39. public:
  40. AZ_INSTANCE_DATA(TestInstanceA, "{65CBF1C8-F65F-4A84-8A11-B510BC435DB0}");
  41. AZ_CLASS_ALLOCATOR(TestInstanceA, AZ::SystemAllocator);
  42. TestInstanceA(TestAssetType* asset)
  43. : m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
  44. {
  45. }
  46. Asset<TestAssetType> m_asset;
  47. };
  48. class TestInstanceB : public InstanceData
  49. {
  50. public:
  51. AZ_INSTANCE_DATA(TestInstanceB, "{4ED0A8BF-7800-44B2-AC73-2CB759C61C37}");
  52. AZ_CLASS_ALLOCATOR(TestInstanceB, AZ::SystemAllocator);
  53. TestInstanceB(TestAssetType* asset)
  54. : m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
  55. {
  56. }
  57. ~TestInstanceB()
  58. {
  59. if (m_onDeleteCallback)
  60. {
  61. m_onDeleteCallback();
  62. }
  63. }
  64. Asset<TestAssetType> m_asset;
  65. AZStd::function<void()> m_onDeleteCallback;
  66. };
  67. // test asset handler
  68. template<typename AssetDataT>
  69. class MyAssetHandler : public AssetHandler
  70. {
  71. public:
  72. AZ_CLASS_ALLOCATOR(MyAssetHandler, AZ::SystemAllocator);
  73. AssetPtr CreateAsset(const AssetId& id, const AssetType& type) override
  74. {
  75. (void)id;
  76. EXPECT_TRUE(type == AzTypeInfo<AssetDataT>::Uuid());
  77. if (type == AzTypeInfo<AssetDataT>::Uuid())
  78. {
  79. return aznew AssetDataT();
  80. }
  81. return nullptr;
  82. }
  83. LoadResult LoadAssetData(const Asset<AssetData>&, AZStd::shared_ptr<AssetDataStream>, const AZ::Data::AssetFilterCB&) override
  84. {
  85. return LoadResult::Error;
  86. }
  87. void DestroyAsset(AssetPtr ptr) override
  88. {
  89. EXPECT_TRUE(ptr->GetType() == AzTypeInfo<AssetDataT>::Uuid());
  90. delete ptr;
  91. }
  92. void GetHandledAssetTypes(AZStd::vector<AssetType>& assetTypes) override
  93. {
  94. assetTypes.push_back(AzTypeInfo<AssetDataT>::Uuid());
  95. }
  96. };
  97. class InstanceDatabaseTest : public LeakDetectionFixture
  98. {
  99. protected:
  100. MyAssetHandler<TestAssetType>* m_assetHandler;
  101. public:
  102. void SetUp() override
  103. {
  104. LeakDetectionFixture::SetUp();
  105. // create the asset database
  106. {
  107. AssetManager::Descriptor desc;
  108. AssetManager::Create(desc);
  109. }
  110. // create the instance database
  111. {
  112. InstanceHandler<TestInstanceA> instanceHandler;
  113. instanceHandler.m_createFunction = [](AssetData* assetData)
  114. {
  115. EXPECT_TRUE(azrtti_istypeof<TestAssetType>(assetData));
  116. return aznew TestInstanceA(static_cast<TestAssetType*>(assetData));
  117. };
  118. InstanceDatabase<TestInstanceA>::Create(azrtti_typeid<TestAssetType>(), instanceHandler);
  119. }
  120. // create and register an asset handler
  121. m_assetHandler = aznew MyAssetHandler<TestAssetType>;
  122. AssetManager::Instance().RegisterHandler(m_assetHandler, AzTypeInfo<TestAssetType>::Uuid());
  123. }
  124. void TearDown() override
  125. {
  126. // destroy the database
  127. AssetManager::Destroy();
  128. InstanceDatabase<TestInstanceA>::Destroy();
  129. LeakDetectionFixture::TearDown();
  130. }
  131. };
  132. TEST_F(InstanceDatabaseTest, InstanceCreate)
  133. {
  134. auto& assetManager = AssetManager::Instance();
  135. auto& instanceDatabase = InstanceDatabase<TestInstanceA>::Instance();
  136. Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
  137. Instance<TestInstanceA> instance = instanceDatabase.Find(s_instanceId0);
  138. EXPECT_EQ(instance, nullptr);
  139. instance = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
  140. EXPECT_NE(instance, nullptr);
  141. Instance<TestInstanceA> instance2 = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
  142. EXPECT_EQ(instance, instance2);
  143. Instance<TestInstanceA> instance3 = instanceDatabase.Find(s_instanceId0);
  144. EXPECT_EQ(instance, instance3);
  145. }
  146. TEST_F(InstanceDatabaseTest, InstanceOrphan)
  147. {
  148. auto& assetManager = AssetManager::Instance();
  149. auto& instanceDatabase = InstanceDatabase<TestInstanceA>::Instance();
  150. Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
  151. Instance<TestInstanceA> orphanedInstance = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
  152. EXPECT_NE(orphanedInstance, nullptr);
  153. instanceDatabase.TEMPOrphan(s_instanceId0);
  154. // After orphan, the instance should not be found in the database, but it should still be valid
  155. EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
  156. EXPECT_NE(orphanedInstance, nullptr);
  157. instanceDatabase.TEMPOrphan(s_instanceId0);
  158. // Orphaning twice should be a no-op
  159. EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
  160. EXPECT_NE(orphanedInstance, nullptr);
  161. Instance<TestInstanceA> instance2 = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
  162. // Creating another instance with the same id should return a different instance than the one that was orphaned
  163. EXPECT_NE(orphanedInstance, instance2);
  164. }
  165. enum class ParallelInstanceTestCases
  166. {
  167. Create,
  168. CreateAndDeferRemoval,
  169. CreateAndOrphan,
  170. CreateDeferRemovalAndOrphan
  171. };
  172. enum class ParralleInstanceCurrentAction
  173. {
  174. Create,
  175. DeferredRemoval,
  176. Orphan
  177. };
  178. ParralleInstanceCurrentAction ParallelInstanceGetCurrentAction(const ParallelInstanceTestCases& testCase)
  179. {
  180. switch (testCase)
  181. {
  182. case ParallelInstanceTestCases::CreateAndDeferRemoval:
  183. switch (rand() % 2)
  184. {
  185. case 0: return ParralleInstanceCurrentAction::Create;
  186. case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
  187. }
  188. case ParallelInstanceTestCases::CreateAndOrphan:
  189. switch (rand() % 2)
  190. {
  191. case 0: return ParralleInstanceCurrentAction::Create;
  192. case 1: return ParralleInstanceCurrentAction::Orphan;
  193. }
  194. case ParallelInstanceTestCases::CreateDeferRemovalAndOrphan:
  195. switch (rand() % 3)
  196. {
  197. case 0: return ParralleInstanceCurrentAction::Create;
  198. case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
  199. case 2: return ParralleInstanceCurrentAction::Orphan;
  200. }
  201. case ParallelInstanceTestCases::Create:
  202. default:
  203. return ParralleInstanceCurrentAction::Create;
  204. }
  205. }
  206. void ParallelInstanceCreateHelper(const size_t& threadCountMax, const size_t& assetIdCount, const uint32_t& iterations, const ParallelInstanceTestCases& testCase)
  207. {
  208. //printf("Testing threads=%zu assetIds=%zu ... ", threadCountMax, assetIdCount);
  209. AZ::Debug::Timer timer;
  210. timer.Stamp();
  211. auto& assetManager = AssetManager::Instance();
  212. auto& instanceManager = InstanceDatabase<TestInstanceA>::Instance();
  213. AZStd::vector<Uuid> guids;
  214. AZStd::vector<Data::Instance<Data::InstanceData>> instances;
  215. AZStd::vector<Asset<TestAssetType>> assets;
  216. for (size_t i = 0; i < assetIdCount; ++i)
  217. {
  218. Uuid guid = Uuid::CreateRandom();
  219. guids.emplace_back(guid);
  220. instances.emplace_back(nullptr);
  221. // Pre-create asset so we don't attempt to load it from the catalog.
  222. assets.emplace_back(assetManager.CreateAsset<TestAssetType>(guid, AZ::Data::AssetLoadBehavior::Default));
  223. }
  224. AZStd::vector<AZStd::thread> threads;
  225. AZStd::mutex mutex;
  226. AZStd::mutex referenceTableMutex;
  227. AZStd::atomic<int> threadCount((int)threadCountMax);
  228. AZStd::condition_variable cv;
  229. AZStd::atomic_bool keepDispatching(true);
  230. auto dispatch = [&keepDispatching]()
  231. {
  232. while (keepDispatching)
  233. {
  234. AssetManager::Instance().DispatchEvents();
  235. }
  236. };
  237. srand(0);
  238. AZStd::thread dispatchThread(dispatch);
  239. for (size_t i = 0; i < threadCountMax; ++i)
  240. {
  241. threads.emplace_back(
  242. [&instanceManager, &threadCount, &cv, &guids, &instances, &assets, &iterations, &testCase, &referenceTableMutex]()
  243. {
  244. bool deferRemoval = testCase == ParallelInstanceTestCases::CreateAndDeferRemoval ||
  245. testCase == ParallelInstanceTestCases::CreateDeferRemovalAndOrphan;
  246. for (uint32_t i = 0; i < iterations; ++i) // queue up a bunch of work
  247. {
  248. const size_t index = rand() % guids.size();
  249. const Uuid uuid = guids[index];
  250. const AssetId assetId{ uuid };
  251. const InstanceId instanceId{ InstanceId::CreateFromAssetId(assetId) };
  252. ParralleInstanceCurrentAction currentAction = ParallelInstanceGetCurrentAction(testCase);
  253. if (currentAction == ParralleInstanceCurrentAction::Orphan)
  254. {
  255. // Orphan the instance, but don't decrease its refcount
  256. instanceManager.TEMPOrphan(instanceId);
  257. }
  258. else if (currentAction == ParralleInstanceCurrentAction::DeferredRemoval)
  259. {
  260. // Drop the refcount to zero so the instance will be released
  261. referenceTableMutex.lock();
  262. instances[index] = nullptr;
  263. referenceTableMutex.unlock();
  264. }
  265. else
  266. {
  267. // Otherwise, add a new instance
  268. Instance<TestInstanceA> instance = instanceManager.FindOrCreate(instanceId, assets[index]);
  269. EXPECT_NE(instance, nullptr);
  270. EXPECT_EQ(instance->GetId(), instanceId);
  271. EXPECT_EQ(instance->m_asset, assets[index]);
  272. if (deferRemoval)
  273. {
  274. // Keep a reference to the instance alive so it can be removed later
  275. referenceTableMutex.lock();
  276. instances[index] = instance;
  277. referenceTableMutex.unlock();
  278. }
  279. }
  280. }
  281. threadCount--;
  282. cv.notify_one();
  283. });
  284. }
  285. bool timedOut = false;
  286. // Used to detect a deadlock. If we wait for more than 10 seconds, it's likely a deadlock has occurred
  287. while (threadCount > 0 && !timedOut)
  288. {
  289. AZStd::unique_lock<AZStd::mutex> lock(mutex);
  290. timedOut =
  291. (AZStd::cv_status::timeout ==
  292. cv.wait_until(lock, AZStd::chrono::steady_clock::now() + AZStd::chrono::seconds(1)));
  293. }
  294. EXPECT_TRUE(threadCount == 0) << "One or more threads appear to be deadlocked at " << timer.GetDeltaTimeInSeconds() << " seconds";
  295. for (auto& thread : threads)
  296. {
  297. thread.join();
  298. }
  299. keepDispatching = false;
  300. dispatchThread.join();
  301. //printf("Took %f seconds\n", timer.GetDeltaTimeInSeconds());
  302. }
  303. void ParallelCreateTest(const ParallelInstanceTestCases& testCase)
  304. {
  305. // This is the original test scenario from when InstanceDatabase was first implemented
  306. // threads, AssetIds, seconds
  307. ParallelInstanceCreateHelper(8, 100, 5, testCase);
  308. // This value is checked in as 1 so this test doesn't take too much time, but can be increased locally to soak the test.
  309. const size_t attempts = 1;
  310. for (size_t i = 0; i < attempts; ++i)
  311. {
  312. //printf("Attempt %zu of %zu... \n", i, attempts);
  313. // The idea behind this series of tests is that there are two threads sharing one Instance, and both threads try to
  314. // create or release that instance at the same time.
  315. // At the time, this set of scenarios has something like a 10% failure rate.
  316. const uint32_t iterations = 1000;
  317. // threads, AssetIds, iterations
  318. ParallelInstanceCreateHelper(2, 1, iterations, testCase);
  319. ParallelInstanceCreateHelper(4, 1, iterations, testCase);
  320. ParallelInstanceCreateHelper(8, 1, iterations, testCase);
  321. }
  322. for (size_t i = 0; i < attempts; ++i)
  323. {
  324. //printf("Attempt %zu of %zu... \n", i, attempts);
  325. // Here we try a bunch of different threadCount:assetCount ratios to be thorough
  326. const uint32_t iterations = 1000;
  327. // threads, AssetIds, iterations
  328. ParallelInstanceCreateHelper(2, 1, iterations, testCase);
  329. ParallelInstanceCreateHelper(4, 1, iterations, testCase);
  330. ParallelInstanceCreateHelper(4, 2, iterations, testCase);
  331. ParallelInstanceCreateHelper(4, 4, iterations, testCase);
  332. ParallelInstanceCreateHelper(8, 1, iterations, testCase);
  333. ParallelInstanceCreateHelper(8, 2, iterations, testCase);
  334. ParallelInstanceCreateHelper(8, 3, iterations, testCase);
  335. ParallelInstanceCreateHelper(8, 4, iterations, testCase);
  336. }
  337. }
  338. TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
  339. {
  340. ParallelCreateTest(ParallelInstanceTestCases::Create);
  341. }
  342. TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndDeferRemoval)
  343. {
  344. ParallelCreateTest(ParallelInstanceTestCases::CreateAndDeferRemoval);
  345. }
  346. TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndOrphan)
  347. {
  348. ParallelCreateTest(ParallelInstanceTestCases::CreateAndOrphan);
  349. }
  350. TEST_F(InstanceDatabaseTest, ParallelInstanceCreateDeferRemovalAndOrphan)
  351. {
  352. ParallelCreateTest(ParallelInstanceTestCases::CreateDeferRemovalAndOrphan);
  353. }
  354. TEST_F(InstanceDatabaseTest, InstanceCreateNoDatabase)
  355. {
  356. bool m_deleted = false;
  357. {
  358. Instance<TestInstanceB> instance = aznew TestInstanceB(nullptr);
  359. EXPECT_FALSE(instance->GetId().IsValid());
  360. // Tests whether the deleter actually calls delete properly without
  361. // a parent database.
  362. instance->m_onDeleteCallback = [&m_deleted]()
  363. {
  364. m_deleted = true;
  365. };
  366. }
  367. EXPECT_TRUE(m_deleted);
  368. }
  369. TEST_F(InstanceDatabaseTest, InstanceCreateMultipleDatabases)
  370. {
  371. // create a second instance database.
  372. {
  373. InstanceHandler<TestInstanceB> instanceHandler;
  374. instanceHandler.m_createFunction = [](AssetData* assetData)
  375. {
  376. EXPECT_TRUE(azrtti_istypeof<TestAssetType>(assetData));
  377. return aznew TestInstanceB(static_cast<TestAssetType*>(assetData));
  378. };
  379. InstanceDatabase<TestInstanceB>::Create(azrtti_typeid<TestAssetType>(), instanceHandler);
  380. }
  381. auto& assetManager = AssetManager::Instance();
  382. auto& instanceDatabaseA = InstanceDatabase<TestInstanceA>::Instance();
  383. auto& instanceDatabaseB = InstanceDatabase<TestInstanceB>::Instance();
  384. {
  385. Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
  386. // Run the creation tests on 'A' first.
  387. Instance<TestInstanceA> instanceA = instanceDatabaseA.Find(s_instanceId0);
  388. EXPECT_EQ(instanceA, nullptr);
  389. instanceA = instanceDatabaseA.FindOrCreate(s_instanceId0, someAsset);
  390. EXPECT_NE(instanceA, nullptr);
  391. Instance<TestInstanceA> instanceA2 = instanceDatabaseA.FindOrCreate(s_instanceId0, someAsset);
  392. EXPECT_EQ(instanceA, instanceA2);
  393. Instance<TestInstanceA> instanceA3 = instanceDatabaseA.Find(s_instanceId0);
  394. EXPECT_EQ(instanceA, instanceA3);
  395. // Run the same test on 'B' to make sure it works independently.
  396. Instance<TestInstanceB> instanceB = instanceDatabaseB.Find(s_instanceId0);
  397. EXPECT_EQ(instanceB, nullptr);
  398. instanceB = instanceDatabaseB.FindOrCreate(s_instanceId0, someAsset);
  399. EXPECT_NE(instanceB, nullptr);
  400. Instance<TestInstanceB> instanceB2 = instanceDatabaseB.FindOrCreate(s_instanceId0, someAsset);
  401. EXPECT_EQ(instanceB, instanceB2);
  402. Instance<TestInstanceB> instanceB3 = instanceDatabaseB.Find(s_instanceId0);
  403. EXPECT_EQ(instanceB, instanceB3);
  404. }
  405. InstanceDatabase<TestInstanceB>::Destroy();
  406. }
  407. } // namespace UnitTest