InstanceDeserializationTests.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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 <AzToolsFramework/ToolsComponents/TransformComponent.h>
  9. #include <AzToolsFramework/Prefab/PrefabDomUtils.h>
  10. #include <Prefab/PrefabTestFixture.h>
  11. namespace UnitTest
  12. {
  13. using InstanceDeserializationTest = PrefabTestFixture;
  14. using InstanceUniquePointer = AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>;
  15. static void GenerateDomAndReloadInstantiatedPrefab(InstanceUniquePointer& createdPrefab, InstanceUniquePointer& instantiatedPrefab)
  16. {
  17. PrefabDom tempDomForStoringAndLoading;
  18. PrefabDomUtils::StoreInstanceInPrefabDom(*createdPrefab, tempDomForStoringAndLoading);
  19. PrefabDomUtils::LoadInstanceFromPrefabDom(
  20. *instantiatedPrefab, tempDomForStoringAndLoading, PrefabDomUtils::LoadFlags::UseSelectiveDeserialization);
  21. }
  22. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> SetupPrefabInstances(
  23. const AzToolsFramework::EntityList& entitiesToUseForCreation,
  24. AZStd::vector<AZStd::unique_ptr<Instance>>&& nestedInstances, PrefabSystemComponent* prefabSystemComponent)
  25. {
  26. AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> createdPrefab =
  27. prefabSystemComponent->CreatePrefab(entitiesToUseForCreation, AZStd::move(nestedInstances), "test/path");
  28. EXPECT_NE(nullptr, createdPrefab);
  29. AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> instantiatedPrefab =
  30. prefabSystemComponent->InstantiatePrefab(createdPrefab->GetTemplateId());
  31. EXPECT_NE(nullptr, instantiatedPrefab);
  32. instantiatedPrefab->GetAllEntitiesInHierarchy(
  33. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  34. {
  35. // Activate the entities so that we can later validate that entities stay activated throughout the deserialization.
  36. entity->Init();
  37. entity->Activate();
  38. return true;
  39. });
  40. return { AZStd::move(createdPrefab), AZStd::move(instantiatedPrefab) };
  41. }
  42. static void ValidateEntityState(
  43. const InstanceUniquePointer& instanceToLookUnder, AZStd::string_view entityName, AZ::Entity::State expectedEntityState)
  44. {
  45. bool isEntityFound = false;
  46. instanceToLookUnder->GetEntities(
  47. [&isEntityFound, &entityName, &expectedEntityState](const AZStd::unique_ptr<AZ::Entity>& entity)
  48. {
  49. if (entity->GetName() == entityName)
  50. {
  51. EXPECT_EQ(entity->GetState(), expectedEntityState);
  52. isEntityFound = true;
  53. }
  54. return true;
  55. });
  56. EXPECT_TRUE(isEntityFound);
  57. }
  58. static void ValidateTransformComponentValue(const AZStd::unique_ptr<AZ::Entity>& entity, const float worldXValue)
  59. {
  60. // Validate that the entity is in 'constructed' state, which indicates that it got reloaded.
  61. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Constructed);
  62. AZ::Entity::ComponentArrayType entityComponents = entity->GetComponents();
  63. EXPECT_EQ(1, entityComponents.size());
  64. AzToolsFramework::Components::TransformComponent* transformComponentInInstantiatedPrefab =
  65. reinterpret_cast<AzToolsFramework::Components::TransformComponent*>(entityComponents.front());
  66. EXPECT_NE(nullptr, transformComponentInInstantiatedPrefab);
  67. // Validate that the transform component is correctly updated after reloading.
  68. EXPECT_EQ(transformComponentInInstantiatedPrefab->GetWorldX(), worldXValue);
  69. }
  70. TEST_F(InstanceDeserializationTest, ReloadInstanceUponComponentUpdate)
  71. {
  72. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  73. AzToolsFramework::Components::TransformComponent* transformComponent = aznew AzToolsFramework::Components::TransformComponent;
  74. transformComponent->SetWorldTranslation(AZ::Vector3(10.0, 0, 0));
  75. entity1->AddComponent(transformComponent);
  76. AZ::Entity* entity2 = CreateEntity("Entity2", false);
  77. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  78. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1, entity2 }, {}, m_prefabSystemComponent);
  79. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  80. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  81. createdPrefab->GetEntities(
  82. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  83. {
  84. if (entity->GetName() == "Entity1")
  85. {
  86. // Activate the entity to access the transform interface and use it to modify the transform component.
  87. entity->Init();
  88. entity->Activate();
  89. AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetWorldX, 20.0f);
  90. }
  91. return true;
  92. });
  93. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  94. instantiatedPrefab->GetEntities(
  95. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  96. {
  97. if (entity->GetName() == "Entity2")
  98. {
  99. // Since we didn't touch entity2 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  100. // active state.
  101. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  102. }
  103. else if (entity->GetName() == "Entity1")
  104. {
  105. ValidateTransformComponentValue(entity, 20.0f);
  106. }
  107. return true;
  108. });
  109. }
  110. TEST_F(InstanceDeserializationTest, ReloadInstanceWithCachedDom)
  111. {
  112. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  113. AzToolsFramework::Components::TransformComponent* transformComponent = aznew AzToolsFramework::Components::TransformComponent;
  114. transformComponent->SetWorldTranslation(AZ::Vector3(10.0, 0, 0));
  115. entity1->AddComponent(transformComponent);
  116. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  117. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, {}, m_prefabSystemComponent);
  118. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  119. createdPrefab->GetEntities(
  120. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  121. {
  122. if (entity->GetName() == "Entity1")
  123. {
  124. // Activate the entity to access the transform interface and use it to modify the transform component.
  125. entity->Init();
  126. entity->Activate();
  127. AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetWorldX, 20.0f);
  128. }
  129. return true;
  130. });
  131. PrefabDom tempDomForStoringAndLoading;
  132. PrefabDomUtils::StoreInstanceInPrefabDom(*createdPrefab, tempDomForStoringAndLoading);
  133. createdPrefab->SetCachedInstanceDom(tempDomForStoringAndLoading);
  134. PrefabDomUtils::LoadInstanceFromPrefabDom(
  135. *createdPrefab, tempDomForStoringAndLoading, PrefabDomUtils::LoadFlags::UseSelectiveDeserialization);
  136. createdPrefab->GetEntities(
  137. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  138. {
  139. // Since we updated the cached dom, entities should remain untouched in createdPrefab and thus retain their
  140. // active state.
  141. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  142. return true;
  143. });
  144. }
  145. TEST_F(InstanceDeserializationTest, ReloadInstanceUponComponentAdd)
  146. {
  147. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  148. AZ::Entity* entity2 = CreateEntity("Entity2", false);
  149. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  150. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1, entity2 }, {}, m_prefabSystemComponent);
  151. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  152. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  153. createdPrefab->GetEntities(
  154. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  155. {
  156. if (entity->GetName() == "Entity1")
  157. {
  158. // Add a transform component to entity1 of createdPrefab.
  159. entity->CreateComponent(AZ::EditorTransformComponentTypeId);
  160. }
  161. return true;
  162. });
  163. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  164. instantiatedPrefab->GetEntities(
  165. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  166. {
  167. if (entity->GetName() == "Entity2")
  168. {
  169. // Since we didn't touch entity2 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  170. // active state.
  171. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  172. }
  173. else if (entity->GetName() == "Entity1")
  174. {
  175. // Validate that the entity is in 'constructed' state, which indicates that it got reloaded.
  176. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Constructed);
  177. AZ::Entity::ComponentArrayType entity1Components = entity->GetComponents();
  178. EXPECT_EQ(1, entity1Components.size());
  179. AzToolsFramework::Components::TransformComponent* transformComponentInInstantiatedPrefab =
  180. reinterpret_cast<AzToolsFramework::Components::TransformComponent*>(entity1Components.front());
  181. // Validate that a transform component exists in entity1 of instantiatedPrefab.
  182. EXPECT_TRUE(transformComponentInInstantiatedPrefab != nullptr);
  183. }
  184. return true;
  185. });
  186. }
  187. TEST_F(InstanceDeserializationTest, ReloadInstanceUponComponentDelete)
  188. {
  189. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  190. AzToolsFramework::Components::TransformComponent* transformComponent = aznew AzToolsFramework::Components::TransformComponent;
  191. transformComponent->SetWorldTranslation(AZ::Vector3(10.0, 0, 0));
  192. entity1->AddComponent(transformComponent);
  193. AZ::Entity* entity2 = CreateEntity("Entity2", false);
  194. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  195. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1, entity2 }, {}, m_prefabSystemComponent);
  196. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  197. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  198. createdPrefab->GetEntities(
  199. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  200. {
  201. if (entity->GetName() == "Entity1")
  202. {
  203. // Remove the transform component from entity1 of createdPrefab;
  204. AZ::Component* transformComponent = entity->GetComponents().front();
  205. EXPECT_NE(nullptr, transformComponent);
  206. entity->RemoveComponent(transformComponent);
  207. delete transformComponent;
  208. transformComponent = nullptr;
  209. }
  210. return true;
  211. });
  212. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  213. instantiatedPrefab->GetEntities(
  214. [](const AZStd::unique_ptr<AZ::Entity>& entity)
  215. {
  216. if (entity->GetName() == "Entity2")
  217. {
  218. // Since we didn't touch entity2 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  219. // active state.
  220. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  221. }
  222. else if (entity->GetName() == "Entity1")
  223. {
  224. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Constructed);
  225. AZ::Entity::ComponentArrayType entity1Components = entity->GetComponents();
  226. // Validate that the transform component can't be found in entity1 of instantiatedPrefab.
  227. EXPECT_TRUE(entity1Components.empty());
  228. }
  229. return true;
  230. });
  231. }
  232. TEST_F(InstanceDeserializationTest, ReloadInstanceUponAddingEntityToExistingEntities)
  233. {
  234. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  235. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  236. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, {}, m_prefabSystemComponent);
  237. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  238. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  239. createdPrefab->AddEntity(AZStd::move(AZStd::make_unique<AZ::Entity>("Entity2")));
  240. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  241. bool isEntity1Found = false, isEntity2Found = false;
  242. instantiatedPrefab->GetEntities(
  243. [&isEntity1Found, &isEntity2Found](const AZStd::unique_ptr<AZ::Entity>& entity)
  244. {
  245. if (entity->GetName() == "Entity1")
  246. {
  247. // Since we didn't touch entity1 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  248. // active state.
  249. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  250. isEntity1Found = true;
  251. }
  252. else if (entity->GetName() == "Entity2")
  253. {
  254. // Validate that the entity2 is in 'constructed' state, which indicates that it got added from dom.
  255. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Constructed);
  256. isEntity2Found = true;
  257. }
  258. return true;
  259. });
  260. EXPECT_TRUE(isEntity1Found);
  261. EXPECT_TRUE(isEntity2Found);
  262. }
  263. TEST_F(InstanceDeserializationTest, ReloadInstanceUponAddingTheFirstEntity)
  264. {
  265. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  266. SetupPrefabInstances(AzToolsFramework::EntityList{}, {}, m_prefabSystemComponent);
  267. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  268. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  269. createdPrefab->AddEntity(AZStd::move(AZStd::make_unique<AZ::Entity>("Entity1")));
  270. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  271. bool isEntity1Found = false;
  272. instantiatedPrefab->GetEntities(
  273. [&isEntity1Found](const AZStd::unique_ptr<AZ::Entity>& entity)
  274. {
  275. if (entity->GetName() == "Entity1")
  276. {
  277. isEntity1Found = true;
  278. }
  279. return true;
  280. });
  281. EXPECT_TRUE(isEntity1Found);
  282. }
  283. TEST_F(InstanceDeserializationTest, ReloadInstanceUponDeletingOneAmongManyEntities)
  284. {
  285. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  286. AZ::Entity* entity2 = CreateEntity("Entity2", false);
  287. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  288. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1, entity2 }, {}, m_prefabSystemComponent);
  289. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  290. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  291. createdPrefab->DetachEntity(entity2->GetId());
  292. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  293. bool isEntity1Found = false, isEntity2Found = false;
  294. instantiatedPrefab->GetEntities(
  295. [&isEntity1Found, &isEntity2Found](const AZStd::unique_ptr<AZ::Entity>& entity)
  296. {
  297. if (entity->GetName() == "Entity1")
  298. {
  299. // Since we didn't touch entity1 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  300. // active state.
  301. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  302. isEntity1Found = true;
  303. }
  304. else if (entity->GetName() == "Entity2")
  305. {
  306. // This shouldn't be hit since Entity2 should have been removed. Mark the boolean as true and throw an error later.
  307. isEntity2Found = true;
  308. }
  309. return true;
  310. });
  311. EXPECT_TRUE(isEntity1Found);
  312. EXPECT_FALSE(isEntity2Found);
  313. }
  314. TEST_F(InstanceDeserializationTest, ReloadInstanceUponDeletingTheOnlyEntity)
  315. {
  316. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  317. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  318. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, {}, m_prefabSystemComponent);
  319. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  320. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  321. createdPrefab->DetachEntity(entity1->GetId());
  322. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  323. bool isEntity1Found = false;
  324. instantiatedPrefab->GetEntities(
  325. [&isEntity1Found](const AZStd::unique_ptr<AZ::Entity>& entity)
  326. {
  327. if (entity->GetName() == "Entity1")
  328. {
  329. // Since we didn't touch entity1 in createdPrefab, it should remain untouched in instantiatedPrefab and thus retain its
  330. // active state.
  331. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  332. isEntity1Found = true;
  333. }
  334. return true;
  335. });
  336. EXPECT_FALSE(isEntity1Found);
  337. }
  338. TEST_F(InstanceDeserializationTest, ReloadInstanceUponAddingTheFirstNestedInstance)
  339. {
  340. AZ::Entity* entity1 = CreateEntity("Entity1", false);
  341. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  342. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, {}, m_prefabSystemComponent);
  343. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  344. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  345. AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> nestedPrefab = m_prefabSystemComponent->CreatePrefab(
  346. AzToolsFramework::EntityList{ CreateEntity("Entity1", false) }, {}, "test/nestedPrefabPath");
  347. // Extract the template id from the instance and store it in a variable before moving the instance.
  348. TemplateId nestedPrefabTemplateId = nestedPrefab->GetTemplateId();
  349. createdPrefab->AddInstance(AZStd::move(nestedPrefab));
  350. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  351. // Validate that the entity remains in active state throughout the reloading process. This indicates that it is untouched.
  352. ValidateEntityState(instantiatedPrefab, "Entity1", AZ::Entity::State::Active);
  353. // Validate that there is one instance after reloading the instantiated prefab.
  354. EXPECT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  355. }
  356. TEST_F(InstanceDeserializationTest, ReloadInstanceUponAddingNestedInstanceToExistingNestedInstances)
  357. {
  358. AZ::Entity* entity1 = CreateEntity("EntityUnderParentPrefab", false);
  359. InstanceUniquePointer nestedInstanceToUseForCreation = m_prefabSystemComponent->CreatePrefab(
  360. AzToolsFramework::EntityList{ CreateEntity("EntityUnderNestedPrefab", false) }, {}, "test/nestedPrefabPath");
  361. // Extract the template id from the instance and store it in a variable before moving the instance.
  362. TemplateId nestedPrefabTemplateId = nestedInstanceToUseForCreation->GetTemplateId();
  363. AZStd::vector<InstanceUniquePointer> nestedInstances;
  364. nestedInstances.emplace_back(AZStd::move(nestedInstanceToUseForCreation));
  365. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances = SetupPrefabInstances(
  366. AzToolsFramework::EntityList{ entity1 }, AZStd::move(nestedInstances),
  367. m_prefabSystemComponent);
  368. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  369. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  370. ASSERT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  371. InstanceUniquePointer nestedInstanceToAdd = m_prefabSystemComponent->InstantiatePrefab(nestedPrefabTemplateId);
  372. Instance& instanceAdded = createdPrefab->AddInstance(AZStd::move(nestedInstanceToAdd));
  373. InstanceAlias aliasOfInstanceAdded = instanceAdded.GetInstanceAlias();
  374. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  375. // Validate that the entity remains in active state throughout the reloading process. This indicates that it is untouched.
  376. ValidateEntityState(instantiatedPrefab, "EntityUnderParentPrefab", AZ::Entity::State::Active);
  377. // Validate that there are two instances after reloading the instantiated prefab.
  378. EXPECT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 2);
  379. instantiatedPrefab->GetNestedInstances(
  380. [&aliasOfInstanceAdded](AZStd::unique_ptr<Instance>& nestedInstance)
  381. {
  382. if (nestedInstance->GetInstanceAlias() == aliasOfInstanceAdded)
  383. {
  384. // Entities under a newly deserialized instance should be in constructed state.
  385. ValidateEntityState(nestedInstance, "EntityUnderNestedPrefab", AZ::Entity::State::Constructed);
  386. }
  387. else
  388. {
  389. // Entities under an existing nested instance should be in active state. This indicates that the instance is untouched.
  390. ValidateEntityState(nestedInstance, "EntityUnderNestedPrefab", AZ::Entity::State::Active);
  391. }
  392. });
  393. }
  394. TEST_F(InstanceDeserializationTest, ReloadInstanceUponDeletingTheOnlyNestedInstance)
  395. {
  396. AZ::Entity* entity1 = CreateEntity("EntityUnderParentPrefab", false);
  397. InstanceUniquePointer nestedInstanceToUseForCreation = m_prefabSystemComponent->CreatePrefab(
  398. AzToolsFramework::EntityList{ CreateEntity("EntityUnderNestedPrefab", false) }, {}, "test/nestedPrefabPath");
  399. // Extract the template id from the instance and store it in a variable before moving the instance.
  400. TemplateId nestedPrefabTemplateId = nestedInstanceToUseForCreation->GetTemplateId();
  401. AZStd::vector<InstanceUniquePointer> nestedInstances;
  402. nestedInstances.emplace_back(AZStd::move(nestedInstanceToUseForCreation));
  403. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  404. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, AZStd::move(nestedInstances), m_prefabSystemComponent);
  405. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  406. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  407. ASSERT_EQ(createdPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  408. InstanceUniquePointer nestedInstanceToRemove =
  409. createdPrefab->DetachNestedInstance(createdPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).front());
  410. nestedInstanceToRemove.reset();
  411. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  412. // Validate that the entity remains in active state throughout the reloading process. This indicates that it is untouched.
  413. ValidateEntityState(instantiatedPrefab, "EntityUnderParentPrefab", AZ::Entity::State::Active);
  414. // Validate that the only nested instance was removed.
  415. EXPECT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 0);
  416. }
  417. TEST_F(InstanceDeserializationTest, ReloadInstanceUponDeletingOneAmongManyNestedInstances)
  418. {
  419. AZ::Entity* entity1 = CreateEntity("EntityUnderParentPrefab", false);
  420. InstanceUniquePointer nestedInstance1 = m_prefabSystemComponent->CreatePrefab(
  421. AzToolsFramework::EntityList{ CreateEntity("EntityUnderNestedPrefab", false) }, {}, "test/nestedPrefabPath");
  422. // Extract the template id from the instance and store it in a variable before moving the instance.
  423. TemplateId nestedPrefabTemplateId = nestedInstance1->GetTemplateId();
  424. InstanceUniquePointer nestedInstance2 = m_prefabSystemComponent->InstantiatePrefab(nestedPrefabTemplateId);
  425. AZStd::vector<InstanceUniquePointer> nestedInstances;
  426. nestedInstances.emplace_back(AZStd::move(nestedInstance1));
  427. nestedInstances.emplace_back(AZStd::move(nestedInstance2));
  428. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances =
  429. SetupPrefabInstances(AzToolsFramework::EntityList{ entity1 }, AZStd::move(nestedInstances), m_prefabSystemComponent);
  430. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  431. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  432. AZStd::vector<InstanceAlias> nestedInstanceAliases = createdPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId);
  433. ASSERT_EQ(nestedInstanceAliases.size(), 2);
  434. InstanceUniquePointer nestedInstanceToRemove = createdPrefab->DetachNestedInstance(nestedInstanceAliases.front());
  435. nestedInstanceToRemove.reset();
  436. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  437. // Validate that the entity remains in active state throughout the reloading process. This indicates that it is untouched.
  438. ValidateEntityState(instantiatedPrefab, "EntityUnderParentPrefab", AZ::Entity::State::Active);
  439. // Validate that the number of instances came down to just one.
  440. EXPECT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  441. instantiatedPrefab->GetNestedInstances(
  442. [&nestedInstanceAliases](AZStd::unique_ptr<Instance>& nestedInstance)
  443. {
  444. if (nestedInstance->GetInstanceAlias() == nestedInstanceAliases.back())
  445. {
  446. // Entities under an existing nested instance should be in active state. This indicates that the instance is untouched.
  447. ValidateEntityState(nestedInstance, "EntityUnderNestedPrefab", AZ::Entity::State::Active);
  448. }
  449. });
  450. }
  451. TEST_F(InstanceDeserializationTest, ReloadInstanceUponNestedInstanceEntityUpdate)
  452. {
  453. AZ::Entity* entityUnderParentPrefab = CreateEntity("EntityUnderParentPrefab", false);
  454. AZ::Entity* entityUnderNestedPrefab = CreateEntity("EntityUnderNestedPrefab", false);
  455. entityUnderNestedPrefab->CreateComponent(AZ::EditorTransformComponentTypeId);
  456. InstanceUniquePointer nestedInstanceToUseForCreation =
  457. m_prefabSystemComponent->CreatePrefab(AzToolsFramework::EntityList{ entityUnderNestedPrefab }, {}, "test/nestedPrefabPath");
  458. // Extract the template id from the instance and store it in a variable before moving the instance.
  459. TemplateId nestedPrefabTemplateId = nestedInstanceToUseForCreation->GetTemplateId();
  460. AZStd::vector<InstanceUniquePointer> nestedInstances;
  461. nestedInstances.emplace_back(AZStd::move(nestedInstanceToUseForCreation));
  462. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances = SetupPrefabInstances(
  463. AzToolsFramework::EntityList{ entityUnderParentPrefab }, AZStd::move(nestedInstances), m_prefabSystemComponent);
  464. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  465. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  466. ASSERT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  467. // Activate the entity to access the transform interface and use it to modify the transform component.
  468. entityUnderNestedPrefab->Init();
  469. entityUnderNestedPrefab->Activate();
  470. AZ::TransformBus::Event(entityUnderNestedPrefab->GetId(), &AZ::TransformInterface::SetWorldX, 20.0f);
  471. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  472. // Validate that the entity remains in active state throughout the reloading process. This indicates that it is untouched.
  473. ValidateEntityState(instantiatedPrefab, "EntityUnderParentPrefab", AZ::Entity::State::Active);
  474. instantiatedPrefab->GetNestedInstances(
  475. [](AZStd::unique_ptr<Instance>& nestedInstance)
  476. {
  477. ValidateEntityState(nestedInstance, "EntityUnderNestedPrefab", AZ::Entity::State::Constructed);
  478. });
  479. }
  480. TEST_F(InstanceDeserializationTest, ReloadInstanceWithoutReloadingNestedInstances)
  481. {
  482. AZ::Entity* entityUnderParentPrefab = CreateEntity("EntityUnderParentPrefab", false);
  483. AZ::Entity* entityUnderNestedPrefab = CreateEntity("EntityUnderNestedPrefab", false);
  484. entityUnderParentPrefab->CreateComponent(AZ::EditorTransformComponentTypeId);
  485. InstanceUniquePointer nestedInstanceToUseForCreation =
  486. m_prefabSystemComponent->CreatePrefab(AzToolsFramework::EntityList{ entityUnderNestedPrefab }, {}, "test/nestedPrefabPath");
  487. // Extract the template id from the instance and store it in a variable before moving the instance.
  488. TemplateId nestedPrefabTemplateId = nestedInstanceToUseForCreation->GetTemplateId();
  489. AZStd::vector<InstanceUniquePointer> nestedInstances;
  490. nestedInstances.emplace_back(AZStd::move(nestedInstanceToUseForCreation));
  491. AZStd::pair<InstanceUniquePointer, InstanceUniquePointer> prefabInstances = SetupPrefabInstances(
  492. AzToolsFramework::EntityList{ entityUnderParentPrefab }, AZStd::move(nestedInstances), m_prefabSystemComponent);
  493. InstanceUniquePointer createdPrefab = AZStd::move(prefabInstances.first);
  494. InstanceUniquePointer instantiatedPrefab = AZStd::move(prefabInstances.second);
  495. ASSERT_EQ(instantiatedPrefab->GetNestedInstanceAliases(nestedPrefabTemplateId).size(), 1);
  496. // Activate the entity to access the transform interface and use it to modify the transform component.
  497. entityUnderParentPrefab->Init();
  498. entityUnderParentPrefab->Activate();
  499. AZ::TransformBus::Event(entityUnderParentPrefab->GetId(), &AZ::TransformInterface::SetWorldX, 20.0f);
  500. GenerateDomAndReloadInstantiatedPrefab(createdPrefab, instantiatedPrefab);
  501. // Validate that the entity under the parent prefab got reloaded.
  502. ValidateEntityState(instantiatedPrefab, "EntityUnderParentPrefab", AZ::Entity::State::Constructed);
  503. instantiatedPrefab->GetNestedInstances(
  504. [](AZStd::unique_ptr<Instance>& nestedInstance)
  505. {
  506. // Validate that the entity under the nested prefab remained untouched.
  507. ValidateEntityState(nestedInstance, "EntityUnderNestedPrefab", AZ::Entity::State::Active);
  508. });
  509. }
  510. } // namespace UnitTest