ComponentAddRemove.cpp 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454
  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/Component/ComponentApplicationBus.h>
  9. #include <AzCore/Outcome/Outcome.h>
  10. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  11. #include <AzCore/UnitTest/TestTypes.h>
  12. #include <AzCore/UserSettings/UserSettingsComponent.h>
  13. #include <AzToolsFramework/API/EntityCompositionNotificationBus.h>
  14. #include <AzToolsFramework/API/EntityCompositionRequestBus.h>
  15. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  16. #include <AzToolsFramework/Application/ToolsApplication.h>
  17. #include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
  18. #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
  19. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  20. #include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
  21. #include <AzToolsFramework/ToolsComponents/EditorDisabledCompositionBus.h>
  22. #include <AzToolsFramework/ToolsComponents/EditorPendingCompositionBus.h>
  23. #include <AzToolsFramework/ToolsComponents/EditorPendingCompositionComponent.h>
  24. #include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
  25. namespace UnitTest
  26. {
  27. using namespace AZ;
  28. using namespace AzToolsFramework;
  29. using namespace AZ::Data;
  30. using namespace AZ::IO;
  31. //
  32. // Declaring several clothing-themed components for use in tests.
  33. //
  34. // Shoes require socks
  35. class LeatherBootsComponent
  36. : public AZ::Component
  37. {
  38. public:
  39. AZ_COMPONENT(LeatherBootsComponent, "{C2852908-0FC6-4BF6-9907-E390840F9897}");
  40. void Activate() override {}
  41. void Deactivate() override {}
  42. static void Reflect(AZ::ReflectContext* reflection)
  43. {
  44. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  45. {
  46. serializeContext->Class<LeatherBootsComponent, AZ::Component>();
  47. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  48. {
  49. editContext->Class<LeatherBootsComponent>("Leather Boots", "")
  50. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  51. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  52. }
  53. }
  54. }
  55. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  56. {
  57. provided.push_back(AZ_CRC_CE("ShoesService"));
  58. }
  59. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  60. {
  61. incompatible.push_back(AZ_CRC_CE("ShoesService"));
  62. }
  63. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  64. {
  65. required.push_back(AZ_CRC_CE("SocksService"));
  66. }
  67. };
  68. // Note that WoolSocksComponent is an "editor component".
  69. // This is just to make sure we are testing with both "editor"
  70. // and "non-editor" components.
  71. class WoolSocksComponent
  72. : public AzToolsFramework::Components::EditorComponentBase
  73. {
  74. public:
  75. AZ_COMPONENT(WoolSocksComponent, "{6436A9A1-701E-4275-AF6F-82F53C7916C8}", EditorComponentBase);
  76. void Activate() override {}
  77. void Deactivate() override {}
  78. static void Reflect(AZ::ReflectContext* reflection)
  79. {
  80. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  81. {
  82. serializeContext->Class<WoolSocksComponent, EditorComponentBase>();
  83. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  84. {
  85. editContext->Class<WoolSocksComponent>("Wool Socks", "")
  86. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  87. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  88. }
  89. }
  90. }
  91. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  92. {
  93. provided.push_back(AZ_CRC_CE("SocksService"));
  94. }
  95. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  96. {
  97. incompatible.push_back(AZ_CRC_CE("SocksService"));
  98. }
  99. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& /*required*/)
  100. {
  101. }
  102. };
  103. // Incompatible with socks
  104. class HatesSocksComponent
  105. : public AZ::Component
  106. {
  107. public:
  108. AZ_COMPONENT(HatesSocksComponent, "{D359D446-A172-4854-8EA9-B95073FF5709}");
  109. void Activate() override {}
  110. void Deactivate() override {}
  111. static void Reflect(AZ::ReflectContext* reflection)
  112. {
  113. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  114. {
  115. serializeContext->Class<HatesSocksComponent, AZ::Component>();
  116. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  117. {
  118. editContext->Class<HatesSocksComponent>("Hates Socks", "")
  119. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  120. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  121. }
  122. }
  123. }
  124. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& /*provided*/)
  125. {
  126. }
  127. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  128. {
  129. incompatible.push_back(AZ_CRC_CE("SocksService"));
  130. }
  131. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& /*required*/)
  132. {
  133. }
  134. };
  135. // Pants require underwear
  136. class BlueJeansComponent
  137. : public AZ::Component
  138. {
  139. public:
  140. AZ_COMPONENT(BlueJeansComponent, "{AEA4D69E-F02B-4F6D-A793-8DEE0C0E54E3}");
  141. void Activate() override {}
  142. void Deactivate() override {}
  143. static void Reflect(AZ::ReflectContext* reflection)
  144. {
  145. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  146. {
  147. serializeContext->Class<BlueJeansComponent, AZ::Component>();
  148. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  149. {
  150. editContext->Class<BlueJeansComponent>("Blue Jeans", "")
  151. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  152. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  153. }
  154. }
  155. }
  156. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  157. {
  158. provided.push_back(AZ_CRC_CE("TrousersService"));
  159. }
  160. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  161. {
  162. incompatible.push_back(AZ_CRC_CE("TrousersService"));
  163. }
  164. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  165. {
  166. required.push_back(AZ_CRC_CE("UnderwearService"));
  167. }
  168. };
  169. // 1 of 2 underwear styles
  170. class WhiteBriefsComponent
  171. : public AZ::Component
  172. {
  173. public:
  174. AZ_COMPONENT(WhiteBriefsComponent, "{8B095E11-082B-4EB1-A119-D1534323C956}");
  175. void Activate() override {}
  176. void Deactivate() override {}
  177. static void Reflect(AZ::ReflectContext* reflection)
  178. {
  179. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  180. {
  181. serializeContext->Class<WhiteBriefsComponent, AZ::Component>();
  182. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  183. {
  184. editContext->Class<WhiteBriefsComponent>("White Briefs", "")
  185. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  186. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  187. }
  188. }
  189. }
  190. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  191. {
  192. provided.push_back(AZ_CRC_CE("UnderwearService"));
  193. }
  194. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  195. {
  196. incompatible.push_back(AZ_CRC_CE("UnderwearService"));
  197. }
  198. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& /*required*/)
  199. {
  200. }
  201. };
  202. // 2 of 2 underwear styles
  203. class HeartBoxersComponent
  204. : public AZ::Component
  205. {
  206. public:
  207. AZ_COMPONENT(HeartBoxersComponent, "{06071955-CC65-4C32-A4D8-1125D827C10B}");
  208. void Activate() override {}
  209. void Deactivate() override {}
  210. static void Reflect(AZ::ReflectContext* reflection)
  211. {
  212. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  213. {
  214. serializeContext->Class<HeartBoxersComponent, AZ::Component>();
  215. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  216. {
  217. editContext->Class<HeartBoxersComponent>("Heart Boxers", "")
  218. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  219. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  220. }
  221. }
  222. }
  223. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  224. {
  225. provided.push_back(AZ_CRC_CE("UnderwearService"));
  226. }
  227. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  228. {
  229. incompatible.push_back(AZ_CRC_CE("UnderwearService"));
  230. }
  231. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& /*required*/)
  232. {
  233. }
  234. };
  235. // Requires a belt (but no belt exists)
  236. class KnifeSheathComponent
  237. : public AZ::Component
  238. {
  239. public:
  240. AZ_COMPONENT(KnifeSheathComponent, "{D99C3EF1-592F-4744-9D07-A5F2CE679870}");
  241. void Activate() override {}
  242. void Deactivate() override {}
  243. static void Reflect(AZ::ReflectContext* reflection)
  244. {
  245. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
  246. {
  247. serializeContext->Class<KnifeSheathComponent, AZ::Component>();
  248. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  249. {
  250. editContext->Class<KnifeSheathComponent>("Knife Sheath", "")
  251. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  252. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  253. }
  254. }
  255. }
  256. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& /*provided*/)
  257. {
  258. }
  259. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& /*incompatible*/)
  260. {
  261. }
  262. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  263. {
  264. required.push_back(AZ_CRC_CE("BeltService"));
  265. }
  266. };
  267. // Count components of given type
  268. template <typename ComponentType>
  269. size_t CountComponentsOnEntity(AZ::Entity* entity)
  270. {
  271. EXPECT_NE(nullptr, entity);
  272. if (!entity)
  273. {
  274. return 0;
  275. }
  276. size_t count = 0;
  277. for (const auto component : entity->GetComponents())
  278. {
  279. if (AzToolsFramework::GetUnderlyingComponentType(*component) == azrtti_typeid<ComponentType>())
  280. {
  281. ++count;
  282. }
  283. }
  284. return count;
  285. }
  286. template <typename ComponentType>
  287. size_t CountPendingComponentsOnEntity(AZ::Entity* entity)
  288. {
  289. EXPECT_NE(nullptr, entity);
  290. if (!entity)
  291. {
  292. return 0;
  293. }
  294. AZ::Entity::ComponentArrayType pendingComponents;
  295. AzToolsFramework::EditorPendingCompositionRequestBus::Event(entity->GetId(), &AzToolsFramework::EditorPendingCompositionRequestBus::Events::GetPendingComponents, pendingComponents);
  296. size_t count = 0;
  297. for (const auto pendingComponent : pendingComponents)
  298. {
  299. if (AzToolsFramework::GetUnderlyingComponentType(*pendingComponent) == azrtti_typeid<ComponentType>())
  300. {
  301. ++count;
  302. }
  303. }
  304. return count;
  305. }
  306. template <typename ComponentType>
  307. size_t CountDisabledComponentsOnEntity(AZ::Entity* entity)
  308. {
  309. EXPECT_NE(nullptr, entity);
  310. if (!entity)
  311. {
  312. return 0;
  313. }
  314. AZ::Entity::ComponentArrayType disabledComponents;
  315. AzToolsFramework::EditorDisabledCompositionRequestBus::Event(entity->GetId(), &AzToolsFramework::EditorDisabledCompositionRequestBus::Events::GetDisabledComponents, disabledComponents);
  316. size_t count = 0;
  317. for (const auto disabledComponent : disabledComponents)
  318. {
  319. if (AzToolsFramework::GetUnderlyingComponentType(*disabledComponent) == azrtti_typeid<ComponentType>())
  320. {
  321. ++count;
  322. }
  323. }
  324. return count;
  325. }
  326. template <typename ComponentType>
  327. AZ::Entity::ComponentArrayType GetComponentsForEntity(AZ::Entity* entity)
  328. {
  329. AZ::Entity::ComponentArrayType components;
  330. AzToolsFramework::GetAllComponentsForEntity(entity, components);
  331. auto itr = AZStd::remove_if(components.begin(), components.end(), [](AZ::Component* component) {return AzToolsFramework::GetUnderlyingComponentType(*component) != azrtti_typeid<ComponentType>();});
  332. components.erase(itr, components.end());
  333. return components;
  334. }
  335. bool CheckAllAreTrue(std::initializer_list<bool> booleans)
  336. {
  337. for (auto boolean : booleans)
  338. {
  339. if (!boolean)
  340. {
  341. return false;
  342. }
  343. }
  344. return true;
  345. }
  346. template <typename... BooleanTypes>
  347. bool CheckAllAreTrue(BooleanTypes... booleans)
  348. {
  349. return CheckAllAreTrue({booleans...});
  350. }
  351. bool DoesComponentListHaveComponent(const AZ::Entity::ComponentArrayType& componentList, const AZ::Uuid& componentType)
  352. {
  353. for (const auto component : componentList)
  354. {
  355. if (AzToolsFramework::GetUnderlyingComponentType(*component) == componentType)
  356. {
  357. return true;
  358. }
  359. }
  360. return false;
  361. }
  362. template <typename... AdditionalValidatedComponentTypes>
  363. struct VerifyAdditionalValidatedComponents
  364. {
  365. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  366. {
  367. auto iterEntityOutcome = outcome.GetValue().find(entity->GetId());
  368. EXPECT_NE(iterEntityOutcome, outcome.GetValue().end());
  369. return CheckAllAreTrue(DoesComponentListHaveComponent(iterEntityOutcome->second.m_additionalValidatedComponents, azrtti_typeid<AdditionalValidatedComponentTypes>())...);
  370. }
  371. };
  372. template <typename... AddedPendingComponentTypes>
  373. struct VerifyAddedPendingComponents
  374. {
  375. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  376. {
  377. auto iterEntityOutcome = outcome.GetValue().find(entity->GetId());
  378. EXPECT_NE(iterEntityOutcome, outcome.GetValue().end());
  379. return CheckAllAreTrue(DoesComponentListHaveComponent(iterEntityOutcome->second.m_addedPendingComponents, azrtti_typeid<AddedPendingComponentTypes>())...);
  380. }
  381. };
  382. template <typename... AddedValidComponentTypes>
  383. struct VerifyAddedValidComponents
  384. {
  385. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  386. {
  387. auto iterEntityOutcome = outcome.GetValue().find(entity->GetId());
  388. EXPECT_NE(iterEntityOutcome, outcome.GetValue().end());
  389. return CheckAllAreTrue(DoesComponentListHaveComponent(iterEntityOutcome->second.m_addedValidComponents, azrtti_typeid<AddedValidComponentTypes>())...);
  390. }
  391. template <typename... AdditionalValidatedComponentTypes>
  392. struct AndAdditionalValidatedComponents
  393. {
  394. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  395. {
  396. return CheckAllAreTrue(
  397. VerifyAddedValidComponents<AddedValidComponentTypes...>::OnOutcomeForEntity(outcome, entity),
  398. VerifyAdditionalValidatedComponents<AdditionalValidatedComponentTypes...>::OnOutcomeForEntity(outcome, entity)
  399. );
  400. }
  401. };
  402. template <typename... AddedPendingComponentTypes>
  403. struct AndAddedPendingComponents
  404. {
  405. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  406. {
  407. return CheckAllAreTrue(
  408. VerifyAddedValidComponents<AddedValidComponentTypes...>::OnOutcomeForEntity(outcome, entity),
  409. VerifyAddedPendingComponents<AddedPendingComponentTypes...>::OnOutcomeForEntity(outcome, entity)
  410. );
  411. }
  412. template <typename... AdditionalValidatedComponentTypes>
  413. struct AndAdditionalValidatedComponents
  414. {
  415. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsOutcome& outcome, AZ::Entity* entity)
  416. {
  417. return CheckAllAreTrue(
  418. VerifyAddedValidComponents<AddedValidComponentTypes...>::OnOutcomeForEntity(outcome, entity),
  419. VerifyAddedPendingComponents<AddedPendingComponentTypes...>::OnOutcomeForEntity(outcome, entity),
  420. VerifyAdditionalValidatedComponents<AdditionalValidatedComponentTypes...>::OnOutcomeForEntity(outcome, entity)
  421. );
  422. }
  423. };
  424. };
  425. };
  426. template <typename... InvalidatedComponentTypes>
  427. struct VerifyRemovalInvalidatedComponents
  428. {
  429. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::RemoveComponentsOutcome& outcome, AZ::Entity* entity)
  430. {
  431. auto iterEntityOutcome = outcome.GetValue().find(entity->GetId());
  432. EXPECT_NE(iterEntityOutcome, outcome.GetValue().end());
  433. return CheckAllAreTrue(DoesComponentListHaveComponent(iterEntityOutcome->second.m_invalidatedComponents, azrtti_typeid<InvalidatedComponentTypes>())...);
  434. }
  435. };
  436. template <typename... ValidatedComponentTypes>
  437. struct VerifyRemovalValidatedComponents
  438. {
  439. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::RemoveComponentsOutcome& outcome, AZ::Entity* entity)
  440. {
  441. auto iterEntityOutcome = outcome.GetValue().find(entity->GetId());
  442. EXPECT_NE(iterEntityOutcome, outcome.GetValue().end());
  443. return CheckAllAreTrue(DoesComponentListHaveComponent(iterEntityOutcome->second.m_validatedComponents, azrtti_typeid<ValidatedComponentTypes>())...);
  444. }
  445. template <typename... InvalidatedComponentTypes>
  446. struct AndInvalidatedComponents
  447. {
  448. static bool OnOutcomeForEntity(const AzToolsFramework::EntityCompositionRequestBus::Events::RemoveComponentsOutcome& outcome, AZ::Entity* entity)
  449. {
  450. return CheckAllAreTrue(
  451. VerifyRemovalValidatedComponents<ValidatedComponentTypes...>::OnOutcomeForEntity(outcome, entity),
  452. VerifyRemovalInvalidatedComponents<InvalidatedComponentTypes...>::OnOutcomeForEntity(outcome, entity)
  453. );
  454. }
  455. };
  456. };
  457. class EntityComponentCounter
  458. {
  459. public:
  460. void SetEntity(const AZ::Entity* entity)
  461. {
  462. m_entity = entity;
  463. }
  464. size_t GetCount() const
  465. {
  466. if (!m_entity)
  467. {
  468. return 0;
  469. }
  470. return GetComponentCount() - m_lastResetCount;
  471. }
  472. size_t GetPendingCount() const
  473. {
  474. if (!m_entity)
  475. {
  476. return 0;
  477. }
  478. return GetPendingComponentCount() - m_lastPendingResetCount;
  479. }
  480. size_t GetDisabledCount() const
  481. {
  482. if (!m_entity)
  483. {
  484. return 0;
  485. }
  486. return GetDisabledComponentCount() - m_lastDisabledResetCount;
  487. }
  488. void Reset()
  489. {
  490. m_lastResetCount = GetComponentCount();
  491. m_lastPendingResetCount = GetPendingComponentCount();
  492. m_lastDisabledResetCount = GetDisabledComponentCount();
  493. }
  494. private:
  495. size_t GetComponentCount() const
  496. {
  497. return m_entity->GetComponents().size();
  498. }
  499. size_t GetPendingComponentCount() const
  500. {
  501. AZ::Entity::ComponentArrayType pendingComponents;
  502. AzToolsFramework::EditorPendingCompositionRequestBus::Event(m_entity->GetId(), &AzToolsFramework::EditorPendingCompositionRequestBus::Events::GetPendingComponents, pendingComponents);
  503. return pendingComponents.size();
  504. }
  505. size_t GetDisabledComponentCount() const
  506. {
  507. AZ::Entity::ComponentArrayType disabledComponents;
  508. AzToolsFramework::EditorDisabledCompositionRequestBus::Event(m_entity->GetId(), &AzToolsFramework::EditorDisabledCompositionRequestBus::Events::GetDisabledComponents, disabledComponents);
  509. return disabledComponents.size();
  510. }
  511. size_t m_lastResetCount = 0;
  512. size_t m_lastPendingResetCount = 0;
  513. size_t m_lastDisabledResetCount = 0;
  514. const AZ::Entity* m_entity;
  515. };
  516. class AddComponentsTest
  517. : public LeakDetectionFixture
  518. {
  519. public:
  520. void SetUp() override
  521. {
  522. LeakDetectionFixture::SetUp();
  523. AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
  524. auto projectPathKey =
  525. AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
  526. AZ::IO::FixedMaxPath enginePath;
  527. registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  528. registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
  529. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
  530. AzFramework::Application::Descriptor descriptor;
  531. AZ::ComponentApplication::StartupParameters startupParameters;
  532. startupParameters.m_loadSettingsRegistry = false;
  533. m_app.Start(descriptor, startupParameters);
  534. // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
  535. // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
  536. // in the unit tests.
  537. AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
  538. m_app.RegisterComponentDescriptor(LeatherBootsComponent::CreateDescriptor());
  539. m_app.RegisterComponentDescriptor(WoolSocksComponent::CreateDescriptor());
  540. m_app.RegisterComponentDescriptor(HatesSocksComponent::CreateDescriptor());
  541. m_app.RegisterComponentDescriptor(BlueJeansComponent::CreateDescriptor());
  542. m_app.RegisterComponentDescriptor(WhiteBriefsComponent::CreateDescriptor());
  543. m_app.RegisterComponentDescriptor(HeartBoxersComponent::CreateDescriptor());
  544. m_app.RegisterComponentDescriptor(KnifeSheathComponent::CreateDescriptor());
  545. m_entity1 = new AZ::Entity("Entity1");
  546. m_entity1Counter.SetEntity(m_entity1);
  547. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *m_entity1);
  548. m_entity1->Init();
  549. m_entity1Counter.Reset();
  550. m_entity2 = new AZ::Entity("Entity2");
  551. m_entity2Counter.SetEntity(m_entity2);
  552. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *m_entity2);
  553. m_entity2->Init();
  554. m_entity2Counter.Reset();
  555. }
  556. void TearDown() override
  557. {
  558. m_app.Stop();
  559. LeakDetectionFixture::TearDown();
  560. }
  561. AzToolsFramework::ToolsApplication m_app;
  562. AZ::Entity* m_entity1 = nullptr;
  563. EntityComponentCounter m_entity1Counter;
  564. AZ::Entity* m_entity2 = nullptr;
  565. EntityComponentCounter m_entity2Counter;
  566. size_t m_initialComponentCount = 0;
  567. };
  568. TEST_F(AddComponentsTest, AddOneComponentToOneEntity)
  569. {
  570. auto outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1);
  571. // Verify success
  572. ASSERT_TRUE(outcome.IsSuccess());
  573. // Check that the returned result was what we expected
  574. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::OnOutcomeForEntity(outcome, m_entity1));
  575. // Check that we have the component added as expected
  576. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  577. // We do separate count checks in case some other random components were added or something unexpected occurred
  578. ASSERT_EQ(1, m_entity1Counter.GetCount());
  579. // Verify nothing is pending
  580. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  581. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  582. // Verify nothing is disabled
  583. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  584. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  585. auto addComponentsResults = outcome.GetValue()[m_entity1->GetId()];
  586. AzToolsFramework::DisableComponents(addComponentsResults.m_addedValidComponents);
  587. ASSERT_EQ(0, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  588. ASSERT_EQ(0, m_entity1Counter.GetCount());
  589. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  590. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  591. ASSERT_EQ(1, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  592. ASSERT_EQ(1, m_entity1Counter.GetDisabledCount());
  593. AzToolsFramework::EnableComponents(addComponentsResults.m_addedValidComponents);
  594. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  595. ASSERT_EQ(1, m_entity1Counter.GetCount());
  596. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  597. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  598. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  599. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  600. }
  601. TEST_F(AddComponentsTest, AddOneComponentToMultipleEntities)
  602. {
  603. // have one entity activated, we must ensure that it is still activated after add operation
  604. m_entity1->Activate();
  605. // add a component to both the activated and inactive entities
  606. auto outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1, m_entity2);
  607. // Verify outcome
  608. ASSERT_TRUE(outcome.IsSuccess());
  609. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::OnOutcomeForEntity(outcome, m_entity1));
  610. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::OnOutcomeForEntity(outcome, m_entity2));
  611. // Should always be on entity, not pending since services are met
  612. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  613. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity2));
  614. ASSERT_EQ(1, m_entity1Counter.GetCount());
  615. ASSERT_EQ(1, m_entity2Counter.GetCount());
  616. // Nothing is pending
  617. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  618. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity2));
  619. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  620. ASSERT_EQ(0, m_entity2Counter.GetPendingCount());
  621. // Nothing is disabled
  622. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  623. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity2));
  624. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  625. ASSERT_EQ(0, m_entity2Counter.GetDisabledCount());
  626. // Still in original states
  627. ASSERT_EQ(AZ::Entity::State::Active, m_entity1->GetState());
  628. ASSERT_EQ(AZ::Entity::State::Init, m_entity2->GetState());
  629. }
  630. // Add a component which requires another component
  631. TEST_F(AddComponentsTest, ComponentRequiresService)
  632. {
  633. auto outcome = AzToolsFramework::AddComponents<LeatherBootsComponent>::ToEntities(m_entity1);
  634. // Verify outcome
  635. ASSERT_TRUE(outcome.IsSuccess());
  636. ASSERT_TRUE(VerifyAddedPendingComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity1));
  637. // This will be pending since it is missing a socks service
  638. ASSERT_EQ(0, CountComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  639. ASSERT_EQ(0, m_entity1Counter.GetCount());
  640. ASSERT_EQ(1, CountPendingComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  641. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  642. ASSERT_EQ(0, CountDisabledComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  643. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  644. // Satisfy the pending component with wool socks
  645. outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1);
  646. ASSERT_TRUE(outcome.IsSuccess());
  647. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::AndAdditionalValidatedComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity1));
  648. // Should have both on the entity now and no pending
  649. ASSERT_EQ(1, CountComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  650. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  651. ASSERT_EQ(2, m_entity1Counter.GetCount());
  652. ASSERT_EQ(0, CountPendingComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  653. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  654. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  655. ASSERT_EQ(0, CountDisabledComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  656. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  657. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  658. // LeatherBootsComponent should be wrapped in a GenericComponentWrapper
  659. // because it is not an "editor component".
  660. auto* wrapper = m_entity1->FindComponent<AzToolsFramework::Components::GenericComponentWrapper>();
  661. ASSERT_TRUE(wrapper && wrapper->GetTemplate());
  662. ASSERT_EQ(azrtti_typeid<LeatherBootsComponent>(), azrtti_typeid(wrapper->GetTemplate()));
  663. // Try adding a component which requires a service
  664. // that no other component provides.
  665. outcome = AzToolsFramework::AddComponents<KnifeSheathComponent>::ToEntities(m_entity2);
  666. ASSERT_TRUE(outcome.IsSuccess());
  667. ASSERT_TRUE(VerifyAddedPendingComponents<KnifeSheathComponent>::OnOutcomeForEntity(outcome, m_entity2));
  668. // This one will always be pending, never on entity as it will never be satisfied
  669. ASSERT_EQ(0, CountComponentsOnEntity<KnifeSheathComponent>(m_entity2));
  670. ASSERT_EQ(0, m_entity2Counter.GetCount());
  671. ASSERT_EQ(1, CountPendingComponentsOnEntity<KnifeSheathComponent>(m_entity2));
  672. ASSERT_EQ(1, m_entity2Counter.GetPendingCount());
  673. ASSERT_EQ(0, CountDisabledComponentsOnEntity<KnifeSheathComponent>(m_entity2));
  674. ASSERT_EQ(0, m_entity2Counter.GetDisabledCount());
  675. // Check pending status
  676. auto component = outcome.GetValue()[m_entity2->GetId()].m_addedPendingComponents[0];
  677. AzToolsFramework::EntityCompositionRequestBus::Events::PendingComponentInfo pendingComponentInfo;
  678. AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(pendingComponentInfo, &AzToolsFramework::EntityCompositionRequestBus::Events::GetPendingComponentInfo, component);
  679. // Should have one missing service
  680. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices.size(), 1);
  681. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible.size(), 0);
  682. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices.size(), 0);
  683. // And that missing service should be the BeltService
  684. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices[0], AZ_CRC_CE("BeltService"));
  685. // Entity 1 should remain untouched
  686. ASSERT_EQ(2, m_entity1Counter.GetCount());
  687. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  688. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  689. }
  690. // Add a component (jeans) which requires a service (underwear),
  691. // and there are two viable options (boxers or briefs).
  692. TEST_F(AddComponentsTest, ComponentRequiresServiceWithTwoViableOptions)
  693. {
  694. auto outcome = AzToolsFramework::AddComponents<BlueJeansComponent>::ToEntities(m_entity1);
  695. ASSERT_TRUE(outcome.IsSuccess());
  696. ASSERT_TRUE(VerifyAddedPendingComponents<BlueJeansComponent>::OnOutcomeForEntity(outcome, m_entity1));
  697. ASSERT_EQ(0, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  698. ASSERT_EQ(0, m_entity1Counter.GetCount());
  699. ASSERT_EQ(1, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  700. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  701. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  702. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  703. // Check pending status
  704. auto component = outcome.GetValue()[m_entity1->GetId()].m_addedPendingComponents[0];
  705. AzToolsFramework::EntityCompositionRequestBus::Events::PendingComponentInfo pendingComponentInfo;
  706. AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(pendingComponentInfo, &AzToolsFramework::EntityCompositionRequestBus::Events::GetPendingComponentInfo, component);
  707. // Should have one missing service
  708. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices.size(), 1);
  709. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible.size(), 0);
  710. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices.size(), 0);
  711. // And that missing service should be the "UnderwearService"
  712. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices[0], AZ_CRC_CE("UnderwearService"));
  713. outcome = AzToolsFramework::AddComponents<WhiteBriefsComponent>::ToEntities(m_entity1);
  714. ASSERT_TRUE(outcome.IsSuccess());
  715. ASSERT_TRUE(VerifyAddedValidComponents<WhiteBriefsComponent>::AndAdditionalValidatedComponents<BlueJeansComponent>::OnOutcomeForEntity(outcome, m_entity1));
  716. // Save this for later checks
  717. auto whiteBriefsComponent = outcome.GetValue()[m_entity1->GetId()].m_addedValidComponents[0];
  718. ASSERT_EQ(1, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  719. ASSERT_EQ(1, CountComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  720. ASSERT_EQ(2, m_entity1Counter.GetCount());
  721. ASSERT_EQ(0, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  722. ASSERT_EQ(0, CountPendingComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  723. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  724. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  725. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  726. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  727. // Now try adding the second kind of underwear
  728. // (it should be pending because entity already has underwear)
  729. outcome = AzToolsFramework::AddComponents<HeartBoxersComponent>::ToEntities(m_entity1);
  730. ASSERT_TRUE(outcome.IsSuccess());
  731. ASSERT_TRUE(VerifyAddedPendingComponents<HeartBoxersComponent>::OnOutcomeForEntity(outcome, m_entity1));
  732. ASSERT_EQ(1, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  733. ASSERT_EQ(1, CountComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  734. ASSERT_EQ(0, CountComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  735. ASSERT_EQ(2, m_entity1Counter.GetCount());
  736. ASSERT_EQ(0, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  737. ASSERT_EQ(0, CountPendingComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  738. ASSERT_EQ(1, CountPendingComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  739. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  740. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  741. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  742. ASSERT_EQ(0, CountDisabledComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  743. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  744. // Check pending status
  745. component = outcome.GetValue()[m_entity1->GetId()].m_addedPendingComponents[0];
  746. AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(pendingComponentInfo, &AzToolsFramework::EntityCompositionRequestBus::Events::GetPendingComponentInfo, component);
  747. // Should have one incompatible component
  748. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices.size(), 0);
  749. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible.size(), 1);
  750. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices.size(), 0);
  751. // And that incompatible component should be the WhiteBriefsComponent from earlier
  752. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible[0], whiteBriefsComponent);
  753. // disable white briefs component, which should resolve heart briefs, and check container counts
  754. AzToolsFramework::DisableComponents({ whiteBriefsComponent });
  755. ASSERT_EQ(1, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  756. ASSERT_EQ(0, CountComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  757. ASSERT_EQ(1, CountComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  758. ASSERT_EQ(2, m_entity1Counter.GetCount());
  759. ASSERT_EQ(0, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  760. ASSERT_EQ(0, CountPendingComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  761. ASSERT_EQ(0, CountPendingComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  762. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  763. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  764. ASSERT_EQ(1, CountDisabledComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  765. ASSERT_EQ(0, CountDisabledComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  766. ASSERT_EQ(1, m_entity1Counter.GetDisabledCount());
  767. // re-enable white briefs component which is now pending because it's re-added after heart boxers was resolved
  768. AzToolsFramework::EnableComponents({ whiteBriefsComponent });
  769. ASSERT_EQ(1, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  770. ASSERT_EQ(0, CountComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  771. ASSERT_EQ(1, CountComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  772. ASSERT_EQ(2, m_entity1Counter.GetCount());
  773. ASSERT_EQ(0, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  774. ASSERT_EQ(1, CountPendingComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  775. ASSERT_EQ(0, CountPendingComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  776. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  777. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  778. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  779. ASSERT_EQ(0, CountDisabledComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  780. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  781. // Try removing pending component (should be uneventful, but it is a branch internally)
  782. auto removalOutcome = AzToolsFramework::RemoveComponents({ component });
  783. ASSERT_TRUE(removalOutcome.IsSuccess());
  784. ASSERT_EQ(1, CountComponentsOnEntity<BlueJeansComponent>(m_entity1));
  785. ASSERT_EQ(1, CountComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  786. ASSERT_EQ(0, CountComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  787. ASSERT_EQ(2, m_entity1Counter.GetCount());
  788. ASSERT_EQ(0, CountPendingComponentsOnEntity<BlueJeansComponent>(m_entity1));
  789. ASSERT_EQ(0, CountPendingComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  790. ASSERT_EQ(0, CountPendingComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  791. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  792. ASSERT_EQ(0, CountDisabledComponentsOnEntity<BlueJeansComponent>(m_entity1));
  793. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WhiteBriefsComponent>(m_entity1));
  794. ASSERT_EQ(0, CountDisabledComponentsOnEntity<HeartBoxersComponent>(m_entity1));
  795. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  796. }
  797. // Add a component to two entities, where the component requires a service,
  798. // and one entity already has that service, but the other entity does not.
  799. TEST_F(AddComponentsTest, TwoEntitiesWhereOneHasRequiredServiceAndOneDoesNot)
  800. {
  801. // entity1 already has socks
  802. auto outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1);
  803. ASSERT_TRUE(outcome.IsSuccess());
  804. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::OnOutcomeForEntity(outcome, m_entity1));
  805. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  806. ASSERT_EQ(1, m_entity1Counter.GetCount());
  807. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  808. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  809. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  810. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  811. outcome = AzToolsFramework::AddComponents<LeatherBootsComponent>::ToEntities(m_entity1, m_entity2);
  812. ASSERT_TRUE(outcome.IsSuccess());
  813. ASSERT_TRUE(VerifyAddedValidComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity1));
  814. ASSERT_TRUE(VerifyAddedPendingComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity2));
  815. ASSERT_EQ(1, CountComponentsOnEntity<WoolSocksComponent>(m_entity1));
  816. ASSERT_EQ(1, CountComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  817. ASSERT_EQ(2, m_entity1Counter.GetCount());
  818. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity1));
  819. ASSERT_EQ(0, CountPendingComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  820. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  821. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity1));
  822. ASSERT_EQ(0, CountDisabledComponentsOnEntity<LeatherBootsComponent>(m_entity1));
  823. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  824. ASSERT_EQ(0, CountComponentsOnEntity<LeatherBootsComponent>(m_entity2));
  825. ASSERT_EQ(0, CountComponentsOnEntity<WoolSocksComponent>(m_entity2));
  826. ASSERT_EQ(0, m_entity2Counter.GetCount());
  827. ASSERT_EQ(1, CountPendingComponentsOnEntity<LeatherBootsComponent>(m_entity2));
  828. ASSERT_EQ(0, CountPendingComponentsOnEntity<WoolSocksComponent>(m_entity2));
  829. ASSERT_EQ(1, m_entity2Counter.GetPendingCount());
  830. ASSERT_EQ(0, CountDisabledComponentsOnEntity<LeatherBootsComponent>(m_entity2));
  831. ASSERT_EQ(0, CountDisabledComponentsOnEntity<WoolSocksComponent>(m_entity2));
  832. ASSERT_EQ(0, m_entity2Counter.GetDisabledCount());
  833. }
  834. // Test adding a component which requires a service,
  835. // but all candidates which provide that service conflicts with some existing component.
  836. TEST_F(AddComponentsTest, RequiredServiceConflictsWithExistingComponents)
  837. {
  838. auto outcome = AzToolsFramework::AddComponents<HatesSocksComponent>::ToEntities(m_entity1);
  839. ASSERT_TRUE(outcome.IsSuccess());
  840. ASSERT_TRUE(VerifyAddedValidComponents<HatesSocksComponent>::OnOutcomeForEntity(outcome, m_entity1));
  841. // Save this for tests and removal later
  842. AZ::Component* hatesSocksComponent = outcome.GetValue()[m_entity1->GetId()].m_addedValidComponents[0];
  843. // Adding boots
  844. outcome = AzToolsFramework::AddComponents<LeatherBootsComponent>::ToEntities(m_entity1, m_entity2);
  845. ASSERT_TRUE(outcome.IsSuccess());
  846. ASSERT_TRUE(VerifyAddedPendingComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity1));
  847. ASSERT_TRUE(VerifyAddedPendingComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity2));
  848. auto leatherBootsComponent = outcome.GetValue()[m_entity1->GetId()].m_addedPendingComponents[0];
  849. // Add socks to make it valid, but incompatible with HatesSocks on entity 1
  850. outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1, m_entity2);
  851. ASSERT_TRUE(VerifyAddedPendingComponents<WoolSocksComponent>::OnOutcomeForEntity(outcome, m_entity1));
  852. // Socks will work on entity 2 and leather boots should be valid now because of it
  853. ASSERT_TRUE(VerifyAddedValidComponents<WoolSocksComponent>::AndAdditionalValidatedComponents<LeatherBootsComponent>::OnOutcomeForEntity(outcome, m_entity2));
  854. // Save this component for later
  855. auto woolSocksComponent = outcome.GetValue()[m_entity1->GetId()].m_addedPendingComponents[0];
  856. // Check pending status
  857. AzToolsFramework::EntityCompositionRequestBus::Events::PendingComponentInfo pendingComponentInfo;
  858. // First check leather boots, it should indicate it is waiting on woolSocksComponent
  859. AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(pendingComponentInfo, &AzToolsFramework::EntityCompositionRequestBus::Events::GetPendingComponentInfo, leatherBootsComponent);
  860. // Should have one pending component
  861. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices.size(), 0);
  862. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible.size(), 0);
  863. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices.size(), 1);
  864. // And that pending component should be the woolSocksComponent
  865. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices[0], woolSocksComponent);
  866. // Now check the wool socks, they should be incompatible with Hates Socks
  867. AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(pendingComponentInfo, &AzToolsFramework::EntityCompositionRequestBus::Events::GetPendingComponentInfo, woolSocksComponent);
  868. // Should have one incompatible component
  869. ASSERT_EQ(pendingComponentInfo.m_missingRequiredServices.size(), 0);
  870. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible.size(), 1);
  871. ASSERT_EQ(pendingComponentInfo.m_pendingComponentsWithRequiredServices.size(), 0);
  872. // And that incompatible component should be the hatesSocksComponent
  873. ASSERT_EQ(pendingComponentInfo.m_validComponentsThatAreIncompatible[0], hatesSocksComponent);
  874. // Remove HatesSocks from entity 1 to valid the entire entity
  875. auto removalOutcome = AzToolsFramework::RemoveComponents({ hatesSocksComponent });
  876. ASSERT_TRUE(removalOutcome.IsSuccess());
  877. ASSERT_TRUE((VerifyRemovalValidatedComponents<WoolSocksComponent, LeatherBootsComponent>::OnOutcomeForEntity(removalOutcome, m_entity1)));
  878. }
  879. // Test adding, enabling, disabling several components
  880. TEST_F(AddComponentsTest, EnableDisableConflictingServices)
  881. {
  882. auto outcome = AzToolsFramework::AddComponents<LeatherBootsComponent>::ToEntities(m_entity1);
  883. ASSERT_TRUE(outcome.IsSuccess());
  884. ASSERT_EQ(0, m_entity1Counter.GetCount());
  885. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  886. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  887. outcome = AzToolsFramework::AddComponents<HatesSocksComponent>::ToEntities(m_entity1);
  888. ASSERT_TRUE(outcome.IsSuccess());
  889. ASSERT_EQ(1, m_entity1Counter.GetCount());
  890. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  891. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  892. outcome = AzToolsFramework::AddComponents<BlueJeansComponent>::ToEntities(m_entity1);
  893. ASSERT_TRUE(outcome.IsSuccess());
  894. ASSERT_EQ(1, m_entity1Counter.GetCount());
  895. ASSERT_EQ(2, m_entity1Counter.GetPendingCount());
  896. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  897. outcome = AzToolsFramework::AddComponents<WhiteBriefsComponent>::ToEntities(m_entity1);
  898. ASSERT_TRUE(outcome.IsSuccess());
  899. ASSERT_EQ(3, m_entity1Counter.GetCount());
  900. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  901. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  902. outcome = AzToolsFramework::AddComponents<HeartBoxersComponent>::ToEntities(m_entity1);
  903. ASSERT_TRUE(outcome.IsSuccess());
  904. ASSERT_EQ(3, m_entity1Counter.GetCount());
  905. ASSERT_EQ(2, m_entity1Counter.GetPendingCount());
  906. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  907. outcome = AzToolsFramework::AddComponents<KnifeSheathComponent>::ToEntities(m_entity1);
  908. ASSERT_TRUE(outcome.IsSuccess());
  909. ASSERT_EQ(3, m_entity1Counter.GetCount());
  910. ASSERT_EQ(3, m_entity1Counter.GetPendingCount());
  911. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  912. outcome = AzToolsFramework::AddComponents<WoolSocksComponent>::ToEntities(m_entity1);
  913. ASSERT_TRUE(outcome.IsSuccess());
  914. ASSERT_EQ(3, m_entity1Counter.GetCount());
  915. ASSERT_EQ(4, m_entity1Counter.GetPendingCount());
  916. ASSERT_EQ(0, m_entity1Counter.GetDisabledCount());
  917. AzToolsFramework::DisableComponents(GetComponentsForEntity<HatesSocksComponent>(m_entity1));
  918. ASSERT_EQ(4, m_entity1Counter.GetCount());
  919. ASSERT_EQ(2, m_entity1Counter.GetPendingCount());
  920. ASSERT_EQ(1, m_entity1Counter.GetDisabledCount());
  921. AzToolsFramework::DisableComponents(GetComponentsForEntity<HeartBoxersComponent>(m_entity1));
  922. ASSERT_EQ(4, m_entity1Counter.GetCount());
  923. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  924. ASSERT_EQ(2, m_entity1Counter.GetDisabledCount());
  925. AzToolsFramework::DisableComponents(GetComponentsForEntity<KnifeSheathComponent>(m_entity1));
  926. ASSERT_EQ(4, m_entity1Counter.GetCount());
  927. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  928. ASSERT_EQ(3, m_entity1Counter.GetDisabledCount());
  929. AzToolsFramework::DisableComponents(GetComponentsForEntity<WhiteBriefsComponent>(m_entity1));
  930. ASSERT_EQ(2, m_entity1Counter.GetCount());
  931. ASSERT_EQ(1, m_entity1Counter.GetPendingCount());
  932. ASSERT_EQ(4, m_entity1Counter.GetDisabledCount());
  933. AzToolsFramework::EnableComponents(GetComponentsForEntity<HeartBoxersComponent>(m_entity1));
  934. ASSERT_EQ(4, m_entity1Counter.GetCount());
  935. ASSERT_EQ(0, m_entity1Counter.GetPendingCount());
  936. ASSERT_EQ(3, m_entity1Counter.GetDisabledCount());
  937. }
  938. // A reusable testing fixture that ensures basic application services are mocked.
  939. // Provided:
  940. // Basic Component descriptor functionality
  941. // Memory
  942. // Serialize (and Edit) contexts
  943. class MockApplicationFixture
  944. : public LeakDetectionFixture
  945. , public AZ::ComponentApplicationBus::Handler
  946. {
  947. public:
  948. AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
  949. AZStd::vector<const ComponentDescriptor*> m_descriptors;
  950. //////////////////////////////////////////////////////////////////////////
  951. // ComponentApplicationMessages
  952. ComponentApplication* GetApplication() override { return nullptr; }
  953. void RegisterComponentDescriptor(const ComponentDescriptor* descriptor) override
  954. {
  955. m_descriptors.push_back(descriptor);
  956. descriptor->Reflect(m_serializeContext.get());
  957. }
  958. void UnregisterComponentDescriptor(const ComponentDescriptor*) override {}
  959. void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler&) override {}
  960. void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler&) override {}
  961. void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override {}
  962. void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override {}
  963. void SignalEntityActivated(Entity*) override {}
  964. void SignalEntityDeactivated(Entity*) override {}
  965. bool AddEntity(Entity*) override { return true; }
  966. bool RemoveEntity(Entity*) override { return true; }
  967. bool DeleteEntity(const EntityId&) override { return true; }
  968. Entity* FindEntity(const EntityId&) override { return nullptr; }
  969. SerializeContext* GetSerializeContext() override { return m_serializeContext.get(); }
  970. BehaviorContext* GetBehaviorContext() override { return nullptr; }
  971. JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; }
  972. const char* GetEngineRoot() const override { return nullptr; }
  973. const char* GetExecutableFolder() const override { return nullptr; }
  974. void EnumerateEntities(const EntityCallback& /*callback*/) override {}
  975. void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override {}
  976. //////////////////////////////////////////////////////////////////////////
  977. MockApplicationFixture()
  978. : LeakDetectionFixture()
  979. {
  980. }
  981. void SetUp() override
  982. {
  983. LeakDetectionFixture::SetUp();
  984. ComponentApplicationBus::Handler::BusConnect();
  985. AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
  986. m_serializeContext.reset(aznew AZ::SerializeContext(true, true));
  987. Entity::Reflect(m_serializeContext.get());
  988. }
  989. void TearDown() override
  990. {
  991. for (auto descriptor : m_descriptors)
  992. {
  993. delete descriptor;
  994. }
  995. m_descriptors.set_capacity(0);
  996. m_serializeContext.reset();
  997. AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
  998. ComponentApplicationBus::Handler::BusDisconnect();
  999. LeakDetectionFixture::TearDown();
  1000. }
  1001. };
  1002. // add scrubbing capability to the above fixture by adding some components to it.
  1003. class EntityTest_Scrubbing : public MockApplicationFixture
  1004. {
  1005. protected:
  1006. AZStd::unique_ptr<Entity> m_fakeSystemEntity;
  1007. public:
  1008. class VisibleComponent
  1009. : public AzToolsFramework::Components::EditorComponentBase
  1010. {
  1011. public:
  1012. AZ_COMPONENT(VisibleComponent, "{6CEC2D1E-08CF-4609-9BEE-BA9D32B4C223}", AzToolsFramework::Components::EditorComponentBase);
  1013. void Activate() override {}
  1014. void Deactivate() override {}
  1015. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  1016. {
  1017. provided.push_back(AZ_CRC_CE("ValidComponentService"));
  1018. }
  1019. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  1020. {
  1021. incompatible.push_back(AZ_CRC_CE("ValidComponentService"));
  1022. }
  1023. static void Reflect(ReflectContext* reflection)
  1024. {
  1025. SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(reflection);
  1026. if (serializeContext)
  1027. {
  1028. serializeContext->Class<VisibleComponent, AzToolsFramework::Components::EditorComponentBase>();
  1029. AZ::EditContext* ec = serializeContext->GetEditContext();
  1030. ec->Class<VisibleComponent>("Visible Component", "A class that should show up in the property editor")
  1031. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  1032. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
  1033. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
  1034. }
  1035. }
  1036. };
  1037. class HiddenComponent
  1038. : public AzToolsFramework::Components::EditorComponentBase
  1039. {
  1040. public:
  1041. AZ_COMPONENT(HiddenComponent, "{E4D2AD8B-3930-46FC-837A-8DDFCA0FB1AF}", AzToolsFramework::Components::EditorComponentBase);
  1042. static Component* s_wasDeleted;
  1043. ~HiddenComponent() override
  1044. {
  1045. s_wasDeleted = this;
  1046. }
  1047. void Activate() override {}
  1048. void Deactivate() override {}
  1049. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  1050. {
  1051. provided.push_back(AZ_CRC_CE("HiddenComponentService"));
  1052. }
  1053. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  1054. {
  1055. incompatible.push_back(AZ_CRC_CE("HiddenComponentService"));
  1056. }
  1057. static void Reflect(ReflectContext* reflection)
  1058. {
  1059. SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(reflection);
  1060. if (serializeContext)
  1061. {
  1062. serializeContext->Class<HiddenComponent, AzToolsFramework::Components::EditorComponentBase>();
  1063. AZ::EditContext* ec = serializeContext->GetEditContext();
  1064. ec->Class<HiddenComponent>("Hidden Component", "A class that should not show up in the property editor")
  1065. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  1066. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
  1067. ->Attribute(AZ::Edit::Attributes::HideIcon, true)
  1068. ->Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::HideOnAdd | AZ::Edit::SliceFlags::PushWhenHidden);
  1069. }
  1070. }
  1071. };
  1072. void SetUp() override
  1073. {
  1074. MockApplicationFixture::SetUp();
  1075. RegisterComponentDescriptor(VisibleComponent::CreateDescriptor());
  1076. RegisterComponentDescriptor(HiddenComponent::CreateDescriptor());
  1077. RegisterComponentDescriptor(AzToolsFramework::Components::EditorPendingCompositionComponent::CreateDescriptor());
  1078. RegisterComponentDescriptor(AzToolsFramework::Components::EditorEntityActionComponent::CreateDescriptor());
  1079. m_fakeSystemEntity.reset(aznew Entity());
  1080. m_fakeSystemEntity->CreateComponent<AzToolsFramework::Components::EditorEntityActionComponent>();
  1081. m_fakeSystemEntity->Init();
  1082. m_fakeSystemEntity->Activate();
  1083. }
  1084. void TearDown() override
  1085. {
  1086. m_fakeSystemEntity->Deactivate();
  1087. m_fakeSystemEntity.reset();
  1088. MockApplicationFixture::TearDown();
  1089. }
  1090. };
  1091. Component* EntityTest_Scrubbing::HiddenComponent::s_wasDeleted = nullptr;
  1092. TEST_F(EntityTest_Scrubbing, ConflictingVisibleComponents_AreInvalidated)
  1093. {
  1094. // in this test we make sure that visible components (ones which show up on the UI)
  1095. // that conflict with each other are properly disabled and moved to the pending list during scrubbing.
  1096. // Component setup:
  1097. AZ::Entity newEntity;
  1098. AZ::Component* firstValidComponent = aznew VisibleComponent();
  1099. newEntity.AddComponent(firstValidComponent);
  1100. AZ::Component* conflictingVisibleComponent = aznew VisibleComponent();
  1101. newEntity.AddComponent(conflictingVisibleComponent);
  1102. EntityList entities;
  1103. entities.push_back(&newEntity);
  1104. EntityCompositionRequests::ScrubEntitiesOutcome resultValue = AZ::Failure<AZStd::string>("Didn't get called");
  1105. EntityCompositionRequestBus::BroadcastResult(resultValue, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1106. ASSERT_TRUE(resultValue.IsSuccess());
  1107. ASSERT_EQ(resultValue.GetValue().size(), 1);
  1108. EntityCompositionRequests::ScrubEntityResults& resultForThisEntity = resultValue.GetValue()[entities[0]->GetId()];
  1109. EXPECT_EQ(resultForThisEntity.m_invalidatedComponents.size(), 1);
  1110. EXPECT_TRUE(AZStd::find(resultForThisEntity.m_invalidatedComponents.begin(), resultForThisEntity.m_invalidatedComponents.end(), conflictingVisibleComponent) != resultForThisEntity.m_invalidatedComponents.end());
  1111. // The "Validated components" array should be empty becuase it should only list previously invalid components that were somehow validated by the scrubbing.
  1112. EXPECT_EQ(resultForThisEntity.m_validatedComponents.size(), 0);
  1113. // make sure the valid visible one wasn't removed:
  1114. EXPECT_EQ(newEntity.FindComponent(azrtti_typeid<VisibleComponent>()), firstValidComponent);
  1115. }
  1116. TEST_F(EntityTest_Scrubbing, ConflictingHiddenComponents_AreDeleted)
  1117. {
  1118. // in this test we make sure that when an entity contains a conflicting hidden component
  1119. // the hidden component is deleted as part of thes scrubbing.
  1120. // Component setup:
  1121. AZ::Entity newEntity;
  1122. AZ::Component* validHiddenComponent = aznew HiddenComponent();
  1123. AZ::Component* conflictingHiddenComponent = aznew HiddenComponent();
  1124. newEntity.AddComponent(validHiddenComponent);
  1125. newEntity.AddComponent(conflictingHiddenComponent);
  1126. EntityTest_Scrubbing::HiddenComponent::s_wasDeleted = nullptr;
  1127. EntityList entities;
  1128. entities.push_back(&newEntity);
  1129. EntityCompositionRequests::ScrubEntitiesOutcome resultValue = AZ::Failure<AZStd::string>("Didn't get called");
  1130. EntityCompositionRequestBus::BroadcastResult(resultValue, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1131. // we cannnot test anything further if the array is empty or it failed.
  1132. ASSERT_TRUE(resultValue.IsSuccess());
  1133. ASSERT_EQ(resultValue.GetValue().size(), 1);
  1134. EntityCompositionRequests::ScrubEntityResults& resultForThisEntity = resultValue.GetValue()[entities[0]->GetId()];
  1135. // we must NOT find the conflicting component - should have been deleted.
  1136. EXPECT_EQ(EntityTest_Scrubbing::HiddenComponent::s_wasDeleted, conflictingHiddenComponent);
  1137. // we must also not find it on the invalidated list, since it has been deleted.
  1138. EXPECT_EQ(resultForThisEntity.m_invalidatedComponents.size(), 0);
  1139. // The "Validated components" array should be empty becuase it should only list previously invalid components that were somehow validated by the scrubbing.
  1140. EXPECT_EQ(resultForThisEntity.m_validatedComponents.size(), 0);
  1141. // make sure the remaining component on the entity is the correct hidden component
  1142. EXPECT_EQ(newEntity.FindComponents(azrtti_typeid<HiddenComponent>()).size(), 1); // there should only be one of them.
  1143. EXPECT_EQ(newEntity.FindComponent(azrtti_typeid<HiddenComponent>()), validHiddenComponent);
  1144. }
  1145. TEST_F(EntityTest_Scrubbing, NonConflictingVisibleComponents_AreReinstated)
  1146. {
  1147. // in this test we make sure that if a pending component (inactive due to prior problems)
  1148. // no longer has those problems, it is made valid and active when scrubbing occurs.
  1149. // Component setup:
  1150. AZ::Entity newEntity;
  1151. AZ::Component* firstValidComponent = aznew VisibleComponent();
  1152. AZ::Component* conflictingVisibleComponent = aznew VisibleComponent();
  1153. newEntity.AddComponent(firstValidComponent);
  1154. newEntity.AddComponent(conflictingVisibleComponent);
  1155. EntityList entities;
  1156. entities.push_back(&newEntity);
  1157. {
  1158. EntityCompositionRequests::ScrubEntitiesOutcome resultValue = AZ::Failure<AZStd::string>("Didn't get called");
  1159. EntityCompositionRequestBus::BroadcastResult(resultValue, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1160. ASSERT_TRUE(resultValue.IsSuccess());
  1161. ASSERT_EQ(resultValue.GetValue().size(), 1);
  1162. // note that the actual results of the above operation are already verified in another test. now we go further with this
  1163. // and actually delete the original component so that the second one can become valid.
  1164. }
  1165. newEntity.RemoveComponent(firstValidComponent);
  1166. delete firstValidComponent;
  1167. // now re-scrub and expect to see the previously disabled conflicting one become the active one:
  1168. {
  1169. EntityCompositionRequests::ScrubEntitiesOutcome resultValue = AZ::Failure<AZStd::string>("Didn't get called");
  1170. EntityCompositionRequestBus::BroadcastResult(resultValue, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1171. ASSERT_TRUE(resultValue.IsSuccess());
  1172. ASSERT_EQ(resultValue.GetValue().size(), 1);
  1173. EntityCompositionRequests::ScrubEntityResults& resultForThisEntity = resultValue.GetValue()[entities[0]->GetId()];
  1174. // nothing should be invalidated
  1175. EXPECT_EQ(resultForThisEntity.m_invalidatedComponents.size(), 0);
  1176. // the visible component should now be activated.
  1177. ASSERT_EQ(resultForThisEntity.m_validatedComponents.size(), 1);
  1178. EXPECT_EQ(resultForThisEntity.m_validatedComponents[0], conflictingVisibleComponent);
  1179. // make sure its actually active, on the entity.
  1180. EXPECT_EQ(newEntity.FindComponent(azrtti_typeid<VisibleComponent>()), conflictingVisibleComponent);
  1181. }
  1182. }
  1183. TEST_F(EntityTest_Scrubbing, InactiveEntityWithInvalidComponents_AreValidatedByPendingComponents)
  1184. {
  1185. // This test takes an entity with known invalid component setup that has not been activated
  1186. // Adds pending components which will satisfy the invalid component setup
  1187. // And expects scrub entities to succeed in this case.
  1188. // Note - this is an edge case when deserializing a module entity or system entity from the app descriptor
  1189. RegisterComponentDescriptor(LeatherBootsComponent::CreateDescriptor());
  1190. RegisterComponentDescriptor(WoolSocksComponent::CreateDescriptor());
  1191. RegisterComponentDescriptor(AzToolsFramework::Components::GenericComponentWrapper::CreateDescriptor());
  1192. AZ::Entity* testEntity = aznew AZ::Entity("Test Scrubbing Entity");
  1193. testEntity->CreateComponent<AzToolsFramework::Components::EditorPendingCompositionComponent>();
  1194. testEntity->Init(); // init to kick off the pending composition request bus, but don't activate because we have invalid components
  1195. EntityList entities;
  1196. entities.push_back(testEntity);
  1197. // Manually add boots component that requires the socks component, to simulate this being read out of app descriptor
  1198. testEntity->CreateComponent<AzToolsFramework::Components::GenericComponentWrapper>(aznew LeatherBootsComponent());
  1199. EntityCompositionRequests::ScrubEntitiesOutcome scrubResults = AZ::Failure<AZStd::string>("Didn't get called");
  1200. EntityCompositionRequestBus::BroadcastResult(scrubResults, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1201. ASSERT_TRUE(scrubResults.IsSuccess());
  1202. // If component is invalidated by scrubbing, it should now be in the pending set
  1203. AZ::Entity::ComponentArrayType pendingComponents;
  1204. AzToolsFramework::EditorPendingCompositionRequestBus::Event(testEntity->GetId(), &AzToolsFramework::EditorPendingCompositionRequests::GetPendingComponents, pendingComponents);
  1205. ASSERT_EQ(pendingComponents.size(), 1);
  1206. // the boots component should be flagged as invalid, since our entity was not activated
  1207. // Note that this copies the invalidated / validated components array into resultForTestEntity, rather than references
  1208. // as the reference will later become invalid as we reset scrubResults.
  1209. EntityCompositionRequests::ScrubEntityResults resultForTestEntity = scrubResults.GetValue()[entities[0]->GetId()];
  1210. ASSERT_EQ(resultForTestEntity.m_invalidatedComponents.size(), 1);
  1211. // Don't actually want to keep the component in the pending set, so that we can validate the initial problem, so add it back onto the entity
  1212. pendingComponents.clear();
  1213. AZ::Component* invalidComponent = resultForTestEntity.m_invalidatedComponents[0];
  1214. AzToolsFramework::EditorPendingCompositionRequestBus::Event(testEntity->GetId(), &AzToolsFramework::EditorPendingCompositionRequests::RemovePendingComponent, invalidComponent);
  1215. AzToolsFramework::EditorPendingCompositionRequestBus::Event(testEntity->GetId(), &AzToolsFramework::EditorPendingCompositionRequests::GetPendingComponents, pendingComponents);
  1216. ASSERT_TRUE(pendingComponents.empty());
  1217. delete invalidComponent;
  1218. // now add a socks component to the pending set which will fulfill the boots' dependency
  1219. testEntity->CreateComponent<AzToolsFramework::Components::GenericComponentWrapper>(aznew LeatherBootsComponent());
  1220. WoolSocksComponent* woolSocksComponent = aznew WoolSocksComponent();
  1221. woolSocksComponent->SetSerializedIdentifier("WoolSocksComponent"); // pending composition component cannot store an empty serialized identifier.
  1222. AzToolsFramework::EditorPendingCompositionRequestBus::Event(testEntity->GetId(), &AzToolsFramework::EditorPendingCompositionRequests::AddPendingComponent, woolSocksComponent);
  1223. pendingComponents.clear();
  1224. AzToolsFramework::EditorPendingCompositionRequestBus::Event(testEntity->GetId(), &AzToolsFramework::EditorPendingCompositionRequests::GetPendingComponents, pendingComponents);
  1225. ASSERT_EQ(pendingComponents.size(), 1);
  1226. scrubResults = AZ::Failure<AZStd::string>("Didn't get called");
  1227. EntityCompositionRequestBus::BroadcastResult(scrubResults, &EntityCompositionRequestBus::Events::ScrubEntities, entities);
  1228. ASSERT_TRUE(scrubResults.IsSuccess());
  1229. resultForTestEntity = scrubResults.GetValue()[entities[0]->GetId()];
  1230. ASSERT_TRUE(resultForTestEntity.m_invalidatedComponents.empty());
  1231. // this should now succeed because the wool socks component is on the entity
  1232. testEntity->Activate();
  1233. ASSERT_EQ(testEntity->GetState(), Entity::State::Active);
  1234. delete testEntity;
  1235. }
  1236. } // namespace UnitTest