2
0

InstanceDataHierarchy.cpp 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682
  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/Entity.h>
  9. #include <AzCore/Component/Component.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/std/containers/stack.h>
  12. #include <AzCore/std/containers/map.h>
  13. #include <AzCore/std/containers/unordered_map.h>
  14. #include <AzCore/std/containers/set.h>
  15. #include <AzCore/std/containers/unordered_set.h>
  16. #include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
  17. #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
  18. #include <AzCore/Serialization/ObjectStream.h>
  19. #include <AzCore/Serialization/Utils.h>
  20. #include <AzCore/UnitTest/TestTypes.h>
  21. #include <random>
  22. #include <QDebug>
  23. using namespace AZ;
  24. using namespace AZ::IO;
  25. using namespace AZ::Debug;
  26. namespace UnitTest
  27. {
  28. class TestComponent
  29. : public AZ::Component
  30. {
  31. public:
  32. AZ_COMPONENT(TestComponent, "{94D5C952-FD65-4997-B517-F36003F8018A}");
  33. struct SubData
  34. {
  35. AZ_TYPE_INFO(SubData, "{A0165FCA-A311-4FED-B36A-DC5FD2AF2857}");
  36. AZ_CLASS_ALLOCATOR(SubData, AZ::SystemAllocator);
  37. SubData() {}
  38. SubData(int v) : m_int(v) {}
  39. ~SubData() = default;
  40. int m_int = 0;
  41. };
  42. class SerializationEvents
  43. : public AZ::SerializeContext::IEventHandler
  44. {
  45. void OnReadBegin(void* classPtr) override
  46. {
  47. TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
  48. component->m_serializeOnReadBegin++;
  49. }
  50. void OnReadEnd(void* classPtr) override
  51. {
  52. TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
  53. component->m_serializeOnReadEnd++;
  54. }
  55. void OnWriteBegin(void* classPtr) override
  56. {
  57. TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
  58. component->m_serializeOnWriteBegin++;
  59. }
  60. void OnWriteEnd(void* classPtr) override
  61. {
  62. TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
  63. component->m_serializeOnWriteEnd++;
  64. }
  65. };
  66. TestComponent() = default;
  67. ~TestComponent() override
  68. {
  69. for (SubData* data : m_pointerContainer)
  70. {
  71. delete data;
  72. }
  73. }
  74. static void Reflect(AZ::ReflectContext* context)
  75. {
  76. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  77. {
  78. serializeContext->Class<SubData>()
  79. ->Version(1)
  80. ->Field("Int", &SubData::m_int)
  81. ;
  82. serializeContext->Class<TestComponent, AZ::Component>()
  83. ->EventHandler<SerializationEvents>()
  84. ->Version(1)
  85. ->Field("Float", &TestComponent::m_float)
  86. ->Field("String", &TestComponent::m_string)
  87. ->Field("NormalContainer", &TestComponent::m_normalContainer)
  88. ->Field("PointerContainer", &TestComponent::m_pointerContainer)
  89. ->Field("SubData", &TestComponent::m_subData)
  90. ;
  91. if (AZ::EditContext* edit = serializeContext->GetEditContext())
  92. {
  93. edit->Class<TestComponent>("Test Component", "A test component")
  94. ->DataElement(nullptr, &TestComponent::m_float, "Float Field", "A float field")
  95. ->DataElement(nullptr, &TestComponent::m_string, "String Field", "A string field")
  96. ->DataElement(nullptr, &TestComponent::m_normalContainer, "Normal Container", "A container")
  97. ->DataElement(nullptr, &TestComponent::m_pointerContainer, "Pointer Container", "A container")
  98. ->DataElement(nullptr, &TestComponent::m_subData, "Struct Field", "A sub data type")
  99. ;
  100. edit->Class<SubData>("Test Component", "A test component")
  101. ->DataElement(nullptr, &SubData::m_int, "Int Field", "An int")
  102. ;
  103. }
  104. }
  105. }
  106. void Activate() override
  107. {
  108. }
  109. void Deactivate() override
  110. {
  111. }
  112. float m_float = 0.f;
  113. AZStd::string m_string;
  114. AZStd::vector<SubData> m_normalContainer;
  115. AZStd::vector<SubData*> m_pointerContainer;
  116. SubData m_subData;
  117. size_t m_serializeOnReadBegin = 0;
  118. size_t m_serializeOnReadEnd = 0;
  119. size_t m_serializeOnWriteBegin = 0;
  120. size_t m_serializeOnWriteEnd = 0;
  121. };
  122. bool operator==(const TestComponent::SubData& lhs, const TestComponent::SubData& rhs)
  123. {
  124. return lhs.m_int == rhs.m_int;
  125. }
  126. /**
  127. * InstanceDataHierarchyBasicTest
  128. */
  129. class InstanceDataHierarchyBasicTest
  130. : public LeakDetectionFixture
  131. {
  132. public:
  133. InstanceDataHierarchyBasicTest()
  134. {
  135. }
  136. ~InstanceDataHierarchyBasicTest() override
  137. {
  138. }
  139. void run()
  140. {
  141. using namespace AzToolsFramework;
  142. AZ::SerializeContext serializeContext;
  143. serializeContext.CreateEditContext();
  144. Entity::Reflect(&serializeContext);
  145. TestComponent::Reflect(&serializeContext);
  146. // Test building of hierarchies, and copying of data from testEntity1 to testEntity2->
  147. {
  148. AZStd::unique_ptr<AZ::Entity> testEntity1(new AZ::Entity());
  149. testEntity1->CreateComponent<TestComponent>();
  150. AZStd::unique_ptr<AZ::Entity> testEntity2(serializeContext.CloneObject(testEntity1.get()));
  151. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 1);
  152. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 1);
  153. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteBegin == 1);
  154. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteEnd == 1);
  155. testEntity1->FindComponent<TestComponent>()->m_float = 1.f;
  156. testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
  157. testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(2));
  158. testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
  159. testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(2));
  160. // First entity has more entries, so we'll be adding elements to testEntity2->
  161. testEntity2->FindComponent<TestComponent>()->m_float = 2.f;
  162. testEntity2->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
  163. testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
  164. InstanceDataHierarchy idh1;
  165. idh1.AddRootInstance(testEntity1.get());
  166. idh1.Build(&serializeContext, 0);
  167. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 2);
  168. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 2);
  169. InstanceDataHierarchy idh2;
  170. idh2.AddRootInstance(testEntity2.get());
  171. idh2.Build(&serializeContext, 0);
  172. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnReadBegin == 1);
  173. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnReadEnd == 1);
  174. // Verify IDH structure.
  175. InstanceDataNode* root1 = idh1.GetRootNode();
  176. AZ_TEST_ASSERT(root1);
  177. InstanceDataNode* root2 = idh2.GetRootNode();
  178. AZ_TEST_ASSERT(root2);
  179. auto secondChildIter = root1->GetChildren().begin();
  180. AZStd::advance(secondChildIter, 1);
  181. InstanceDataNode::Address addr = secondChildIter->ComputeAddress();
  182. AZ_TEST_ASSERT(!addr.empty());
  183. InstanceDataNode* foundIn2 = idh2.FindNodeByAddress(addr);
  184. AZ_TEST_ASSERT(foundIn2);
  185. // Find the TestComponent in entity1's IDH.
  186. AZStd::stack<InstanceDataNode*> nodeStack;
  187. nodeStack.push(root1);
  188. InstanceDataNode* componentNode1 = nullptr;
  189. while (!nodeStack.empty())
  190. {
  191. InstanceDataNode* node = nodeStack.top();
  192. nodeStack.pop();
  193. if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<TestComponent>::Uuid())
  194. {
  195. componentNode1 = node;
  196. break;
  197. }
  198. for (InstanceDataNode& child : node->GetChildren())
  199. {
  200. nodeStack.push(&child);
  201. }
  202. }
  203. // Verify we found the component node in both hierarchies.
  204. AZ_TEST_ASSERT(componentNode1);
  205. addr = componentNode1->ComputeAddress();
  206. foundIn2 = idh2.FindNodeByAddress(addr);
  207. AZ_TEST_ASSERT(foundIn2);
  208. //// Try copying data from entity 1 to entity 2.
  209. bool result = InstanceDataHierarchy::CopyInstanceData(componentNode1, foundIn2, &serializeContext);
  210. AZ_TEST_ASSERT(result);
  211. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 2);
  212. AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 2);
  213. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteBegin == 2);
  214. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteEnd == 2);
  215. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_normalContainer.size() == 2);
  216. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_pointerContainer.size() == 2);
  217. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_float == 1.f);
  218. }
  219. // Test removal of container elements during instance data copying.
  220. {
  221. AZStd::unique_ptr<AZ::Entity> testEntity1(new AZ::Entity());
  222. testEntity1->CreateComponent<TestComponent>();
  223. AZStd::unique_ptr<AZ::Entity> testEntity2(serializeContext.CloneObject(testEntity1.get()));
  224. // First entity has more in container 1, fewer in container 2 as compared to second entity.
  225. testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
  226. testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(2));
  227. testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
  228. testEntity2->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
  229. testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
  230. testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(2));
  231. // Change a field.
  232. testEntity2->FindComponent<TestComponent>()->m_float = 2.f;
  233. InstanceDataHierarchy idh1;
  234. idh1.AddRootInstance(testEntity1.get());
  235. idh1.Build(&serializeContext, 0);
  236. InstanceDataHierarchy idh2;
  237. idh2.AddRootInstance(testEntity2.get());
  238. idh2.Build(&serializeContext, 0);
  239. InstanceDataNode* root1 = idh1.GetRootNode();
  240. // Find the TestComponent in entity1's IDH.
  241. AZStd::stack<InstanceDataNode*> nodeStack;
  242. nodeStack.push(root1);
  243. InstanceDataNode* componentNode1 = nullptr;
  244. while (!nodeStack.empty())
  245. {
  246. InstanceDataNode* node = nodeStack.top();
  247. nodeStack.pop();
  248. if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<TestComponent>::Uuid())
  249. {
  250. componentNode1 = node;
  251. break;
  252. }
  253. for (InstanceDataNode& child : node->GetChildren())
  254. {
  255. nodeStack.push(&child);
  256. }
  257. }
  258. // Verify we found the component node in both hierarchies.
  259. AZ_TEST_ASSERT(componentNode1);
  260. InstanceDataNode::Address addr = componentNode1->ComputeAddress();
  261. InstanceDataNode* foundIn2 = idh2.FindNodeByAddress(addr);
  262. AZ_TEST_ASSERT(foundIn2);
  263. // Do a comparison test
  264. {
  265. size_t newNodes = 0;
  266. size_t removedNodes = 0;
  267. size_t changedNodes = 0;
  268. InstanceDataHierarchy::CompareHierarchies(componentNode1, foundIn2,
  269. &InstanceDataHierarchy::DefaultValueComparisonFunction,
  270. &serializeContext,
  271. // New node
  272. [&](InstanceDataNode* targetNode, AZStd::vector<AZ::u8>& data)
  273. {
  274. (void)targetNode;
  275. (void)data;
  276. ++newNodes;
  277. },
  278. // Removed node (container element).
  279. [&](const InstanceDataNode* sourceNode, InstanceDataNode* targetNodeParent)
  280. {
  281. (void)sourceNode;
  282. (void)targetNodeParent;
  283. ++removedNodes;
  284. },
  285. // Changed node
  286. [&](const InstanceDataNode* sourceNode, InstanceDataNode* targetNode,
  287. AZStd::vector<AZ::u8>& sourceData, AZStd::vector<AZ::u8>& targetData)
  288. {
  289. (void)sourceNode;
  290. (void)targetNode;
  291. (void)sourceData;
  292. (void)targetData;
  293. ++changedNodes;
  294. }
  295. );
  296. AZ_TEST_ASSERT(newNodes == 2); // 2 because child nodes of new nodes are now also flagged as new
  297. AZ_TEST_ASSERT(removedNodes == 1);
  298. AZ_TEST_ASSERT(changedNodes == 1);
  299. }
  300. //// Try copying data from entity 1 to entity 2.
  301. bool result = InstanceDataHierarchy::CopyInstanceData(componentNode1, foundIn2, &serializeContext);
  302. AZ_TEST_ASSERT(result);
  303. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_normalContainer.size() == 2);
  304. AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_pointerContainer.size() == 1);
  305. }
  306. // Test FindNodeByPartialAddress functionality and Read/Write of InstanceDataNode
  307. {
  308. const AZStd::string testString = "this is a test";
  309. const float testFloat = 123.0f;
  310. const int testInt = 7;
  311. const TestComponent::SubData testSubData(testInt);
  312. const AZStd::vector<TestComponent::SubData> testNormalContainer{ TestComponent::SubData(1), TestComponent::SubData(2), TestComponent::SubData(3) };
  313. // create a test component with some initial values
  314. AZStd::unique_ptr<TestComponent> testComponent(new TestComponent);
  315. testComponent.get()->m_float = testFloat;
  316. testComponent.get()->m_string = testString;
  317. testComponent.get()->m_normalContainer = testNormalContainer;
  318. testComponent.get()->m_subData.m_int = testInt;
  319. // create an InstanceDataHierarchy for the test component
  320. InstanceDataHierarchy idhTestComponent;
  321. idhTestComponent.AddRootInstance(testComponent.get());
  322. idhTestComponent.Build(&serializeContext, 0);
  323. // create some partial addresses to search for fields in InstanceDataHierarchy
  324. // note: reflection serialization context values are used for lookup (crcs stored)
  325. // if a more specific address is required, start from field and work up to structures/components etc
  326. // (see addrSubDataInt below as an example)
  327. InstanceDataNode::Address addrFloat = { AZ_CRC_CE("Float") };
  328. InstanceDataNode::Address addrString = { AZ_CRC_CE("String") };
  329. InstanceDataNode::Address addrNormalContainer = { AZ_CRC_CE("NormalContainer") };
  330. InstanceDataNode::Address addrSubData = { AZ_CRC_CE("SubData") };
  331. InstanceDataNode::Address addrSubDataInt = { AZ_CRC_CE("Int"), AZ_CRC_CE("SubData") };
  332. // find InstanceDataNodes using partial address
  333. InstanceDataNode* foundFloat = idhTestComponent.FindNodeByPartialAddress(addrFloat);
  334. InstanceDataNode* foundString = idhTestComponent.FindNodeByPartialAddress(addrString);
  335. InstanceDataNode* foundNormalContainer = idhTestComponent.FindNodeByPartialAddress(addrNormalContainer);
  336. InstanceDataNode* foundSubData = idhTestComponent.FindNodeByPartialAddress(addrSubData);
  337. InstanceDataNode* foundSubDataInt = idhTestComponent.FindNodeByPartialAddress(addrSubDataInt);
  338. // ensure each has been returned successfully
  339. AZ_TEST_ASSERT(foundFloat);
  340. AZ_TEST_ASSERT(foundString);
  341. AZ_TEST_ASSERT(foundNormalContainer);
  342. AZ_TEST_ASSERT(foundSubData);
  343. AZ_TEST_ASSERT(foundSubDataInt);
  344. // check a case where we know the address is incorrect and we will not find an InstanceDataNode
  345. InstanceDataNode::Address addrInvalid = { AZ_CRC_CE("INVALID") };
  346. InstanceDataNode* foundInvalid = idhTestComponent.FindNodeByPartialAddress(addrInvalid);
  347. AZ_TEST_ASSERT(foundInvalid == nullptr);
  348. ///////////////////////////////////////////////////////////////////////////////
  349. // test the values read from the InstanceDataNodes are the same as the ones our TestComponent were constructed with
  350. float readTestFloat;
  351. foundFloat->Read(readTestFloat);
  352. AZ_TEST_ASSERT(readTestFloat == testFloat);
  353. AZStd::string readTestString;
  354. foundString->Read(readTestString);
  355. AZ_TEST_ASSERT(readTestString == testString);
  356. int readTestInt;
  357. foundSubDataInt->Read(readTestInt);
  358. AZ_TEST_ASSERT(readTestInt == testInt);
  359. TestComponent::SubData readTestSubData;
  360. foundSubData->Read(readTestSubData);
  361. AZ_TEST_ASSERT(readTestSubData == testSubData);
  362. AZStd::vector<TestComponent::SubData> readTestNormalContainer;
  363. foundNormalContainer->Read(readTestNormalContainer);
  364. AZ_TEST_ASSERT(readTestNormalContainer == testNormalContainer);
  365. // create some new test values to write to the InstanceDataNode
  366. const AZStd::string newTestString = "this string has been updated!";
  367. const float newTestFloat = 456.0f;
  368. const int newTestInt = 94;
  369. const TestComponent::SubData newTestSubData(newTestInt);
  370. const AZStd::vector<TestComponent::SubData> newTestNormalContainer{ TestComponent::SubData(20), TestComponent::SubData(40), TestComponent::SubData(60) };
  371. // actually write the values to each InstanceDataNode
  372. foundFloat->Write(newTestFloat);
  373. foundString->Write(newTestString);
  374. foundSubData->Write(newTestSubData);
  375. foundNormalContainer->Write(newTestNormalContainer);
  376. // read the values back to make sure the are the same as the newly set values
  377. AZStd::string updatedTestString;
  378. foundString->Read(updatedTestString);
  379. AZ_TEST_ASSERT(updatedTestString == newTestString);
  380. float updatedTestFloat;
  381. foundFloat->Read(updatedTestFloat);
  382. AZ_TEST_ASSERT(updatedTestFloat == newTestFloat);
  383. TestComponent::SubData updatedTestSubData;
  384. foundSubData->Read(updatedTestSubData);
  385. AZ_TEST_ASSERT(updatedTestSubData == newTestSubData);
  386. AZStd::vector<TestComponent::SubData> updatedNormalContainer;
  387. foundNormalContainer->Read(updatedNormalContainer);
  388. AZ_TEST_ASSERT(updatedNormalContainer == newTestNormalContainer);
  389. }
  390. }
  391. };
  392. static AZ::u8 s_persistentIdCounter = 0;
  393. class InstanceDataHierarchyCopyContainerChangesTest
  394. : public LeakDetectionFixture
  395. {
  396. public:
  397. InstanceDataHierarchyCopyContainerChangesTest()
  398. {
  399. }
  400. ~InstanceDataHierarchyCopyContainerChangesTest() override
  401. {
  402. }
  403. class StructInner
  404. {
  405. public:
  406. AZ_TYPE_INFO(StructInner, "{4BFA2A4F-8568-43AA-941C-8361DBA13CBB}");
  407. AZ::u8 m_persistentId;
  408. AZ::u32 m_value;
  409. StructInner()
  410. {
  411. m_value = 1;
  412. m_persistentId = ++s_persistentIdCounter;
  413. }
  414. static void Reflect(AZ::SerializeContext& context)
  415. {
  416. context.Class<StructInner>()->
  417. PersistentId([](const void* instance) -> u64 { return static_cast<u64>(reinterpret_cast<const StructInner*>(instance)->m_persistentId); })->
  418. Field("Id", &StructInner::m_persistentId)->
  419. Field("Value", &StructInner::m_value)
  420. ;
  421. }
  422. };
  423. class StructOuter
  424. {
  425. public:
  426. AZ_TYPE_INFO(StructOuter, "{FEDCED26-8D5A-41CB-BA97-AB687CF51FC6}");
  427. AZStd::vector<StructInner> m_vector;
  428. StructOuter()
  429. {
  430. }
  431. static void Reflect(AZ::SerializeContext& context)
  432. {
  433. context.Class<StructOuter>()->
  434. Field("Vector", &StructOuter::m_vector)
  435. ;
  436. }
  437. };
  438. void DoCopy(StructOuter& source, StructOuter& target, AZ::SerializeContext& ctx)
  439. {
  440. AzToolsFramework::InstanceDataHierarchy sourceHier;
  441. sourceHier.AddRootInstance(&source, AZ::AzTypeInfo<StructOuter>::Uuid());
  442. sourceHier.Build(&ctx, AZ::SerializeContext::ENUM_ACCESS_FOR_READ);
  443. AzToolsFramework::InstanceDataHierarchy targetHier;
  444. targetHier.AddRootInstance(&target, AZ::AzTypeInfo<StructOuter>::Uuid());
  445. targetHier.Build(&ctx, AZ::SerializeContext::ENUM_ACCESS_FOR_READ);
  446. AzToolsFramework::InstanceDataHierarchy::CopyInstanceData(&sourceHier, &targetHier, &ctx);
  447. }
  448. void VerifyMatch(StructOuter& source, StructOuter& target)
  449. {
  450. AZ_TEST_ASSERT(source.m_vector.size() == target.m_vector.size());
  451. // Make sure that matching elements have the same data (we're using persistent Ids, so order can be whatever).
  452. for (auto& sourceElement : source.m_vector)
  453. {
  454. for (auto& targetElement : target.m_vector)
  455. {
  456. if (targetElement.m_persistentId == sourceElement.m_persistentId)
  457. {
  458. AZ_TEST_ASSERT(targetElement.m_value == sourceElement.m_value);
  459. break;
  460. }
  461. }
  462. }
  463. }
  464. void run()
  465. {
  466. using namespace AzToolsFramework;
  467. AZ::SerializeContext serializeContext;
  468. serializeContext.CreateEditContext();
  469. StructInner::Reflect(serializeContext);
  470. StructOuter::Reflect(serializeContext);
  471. StructOuter outerSource;
  472. StructOuter outerTarget;
  473. StructOuter originalSource;
  474. originalSource.m_vector.emplace_back();
  475. originalSource.m_vector.emplace_back();
  476. originalSource.m_vector.emplace_back();
  477. {
  478. outerSource = originalSource;
  479. DoCopy(outerSource, outerTarget, serializeContext);
  480. AZ_TEST_ASSERT(outerTarget.m_vector.size() == 3);
  481. }
  482. {
  483. outerSource = originalSource;
  484. outerTarget = outerSource;
  485. // Pluck from the start of the array so elements get shifted.
  486. // Also modify something in the last element so it's written to the target.
  487. // This verifies that removals are applied safely alongside data changes.
  488. outerSource.m_vector.erase(outerSource.m_vector.begin());
  489. outerSource.m_vector.begin()->m_value = 2;
  490. DoCopy(outerSource, outerTarget, serializeContext);
  491. VerifyMatch(outerSource, outerTarget);
  492. }
  493. {
  494. outerSource = originalSource;
  495. outerTarget = outerSource;
  496. // Remove an element from the target and SHRINK the array to fit so it's
  497. // guaranteed to grow when the missing element is copied from the source.
  498. // This verifies that additions are being applied safely alongside data changes.
  499. outerTarget.m_vector.erase(outerTarget.m_vector.begin());
  500. outerTarget.m_vector.set_capacity(outerTarget.m_vector.size()); // Force grow on insert
  501. outerSource.m_vector.back().m_value = 5;
  502. DoCopy(outerSource, outerTarget, serializeContext);
  503. VerifyMatch(outerSource, outerTarget);
  504. }
  505. {
  506. outerSource = originalSource;
  507. outerTarget = outerSource;
  508. // Add elements to the source.
  509. // Add an element to the target.
  510. // Change a different element.
  511. // This tests removals, additions, and changes occurring together, with net growth in the target container.
  512. outerSource.m_vector.emplace_back();
  513. outerSource.m_vector.emplace_back();
  514. outerTarget.m_vector.emplace_back();
  515. outerTarget.m_vector.set_capacity(outerTarget.m_vector.size()); // Force grow on insert
  516. outerTarget.m_vector.begin()->m_value = 10;
  517. DoCopy(outerSource, outerTarget, serializeContext);
  518. VerifyMatch(outerSource, outerTarget);
  519. }
  520. }
  521. };
  522. enum class TestEnum
  523. {
  524. Value1 = 0x01,
  525. Value2 = 0x02,
  526. Value3 = 0xFF,
  527. };
  528. }
  529. namespace AZ
  530. {
  531. AZ_TYPE_INFO_SPECIALIZE(UnitTest::TestEnum, "{52DBDCC6-0829-4602-A650-E6FC32AFC5F2}");
  532. }
  533. namespace UnitTest
  534. {
  535. class InstanceDataHierarchyEnumContainerTest
  536. : public LeakDetectionFixture
  537. {
  538. public:
  539. class EnumContainer
  540. {
  541. public:
  542. AZ_TYPE_INFO(EnumContainer, "{7F9EED53-7587-4616-B4A7-10B3AF95475E}");
  543. AZ_CLASS_ALLOCATOR(EnumContainer, AZ::SystemAllocator);
  544. TestEnum m_enum;
  545. AZStd::vector<TestEnum> m_enumVector;
  546. static void Reflect(AZ::SerializeContext& context)
  547. {
  548. context.Class<EnumContainer>()
  549. ->Field("Enum", &EnumContainer::m_enum)
  550. ->Field("EnumVector", &EnumContainer::m_enumVector)
  551. ;
  552. if (EditContext* edit = context.GetEditContext())
  553. {
  554. edit->Enum<UnitTest::TestEnum>("TestEnum", "No Description")
  555. ->Value("Value1", UnitTest::TestEnum::Value1)
  556. ->Value("Value2", UnitTest::TestEnum::Value2)
  557. ->Value("Value3", UnitTest::TestEnum::Value3)
  558. ;
  559. edit->Class<EnumContainer>("Enum Container", "Test container that has an external enum")
  560. ->DataElement(nullptr, &EnumContainer::m_enum, "Enum Field", "An enum value")
  561. ->DataElement(nullptr, &EnumContainer::m_enumVector, "Enum Vector Field", "A vector of enum values")
  562. ;
  563. }
  564. }
  565. };
  566. void run()
  567. {
  568. using namespace AzToolsFramework;
  569. AZ::SerializeContext serializeContext;
  570. serializeContext.CreateEditContext();
  571. EnumContainer::Reflect(serializeContext);
  572. EnumContainer ec;
  573. ec.m_enumVector.emplace_back(UnitTest::TestEnum::Value3);
  574. InstanceDataHierarchy idh;
  575. idh.AddRootInstance(&ec, azrtti_typeid<EnumContainer>());
  576. idh.Build(&serializeContext, 0);
  577. InstanceDataNode* enumNode = idh.FindNodeByPartialAddress({ AZ_CRC_CE("Enum") });
  578. InstanceDataNode* enumVectorNode = idh.FindNodeByPartialAddress({ AZ_CRC_CE("EnumVector") });
  579. ASSERT_NE(enumNode, nullptr);
  580. ASSERT_NE(enumVectorNode, nullptr);
  581. auto getEnumData = [&ec](const AzToolsFramework::InstanceDataNode& node) -> Uuid
  582. {
  583. Uuid id = Uuid::CreateNull();
  584. auto attribute = node.GetElementMetadata()->FindAttribute(AZ_CRC_CE("EnumType"));
  585. auto attributeData = azrtti_cast<AttributeData<AZ::TypeId>*>(attribute);
  586. if (attributeData)
  587. {
  588. id = attributeData->Get(&ec);
  589. }
  590. return id;
  591. };
  592. EXPECT_EQ(getEnumData(*enumNode), RttiTypeId<UnitTest::TestEnum>());
  593. const auto& vectorEntries = enumVectorNode->GetChildren();
  594. ASSERT_EQ(vectorEntries.size(), 1);
  595. EXPECT_EQ(getEnumData(*vectorEntries.begin()), RttiTypeId<UnitTest::TestEnum>());
  596. }
  597. };
  598. class GroupTestComponent : public AZ::Component
  599. {
  600. public:
  601. AZ_COMPONENT(GroupTestComponent, "{C088C81D-D59D-43F1-85F8-B2E591BABA36}")
  602. GroupTestComponent() = default;
  603. struct SubData
  604. {
  605. AZ_TYPE_INFO(SubData, "{983316B5-17C0-476E-9CEB-CA749B3ABE5D}");
  606. AZ_CLASS_ALLOCATOR(SubData, AZ::SystemAllocator);
  607. SubData() {}
  608. explicit SubData(int v) : m_int(v) {}
  609. explicit SubData(bool b) : m_bool(b) {}
  610. explicit SubData(float f) : m_float(f) {}
  611. ~SubData() = default;
  612. float m_float = 0.f;
  613. int m_int = 0;
  614. bool m_bool = true;
  615. };
  616. static void Reflect(AZ::ReflectContext* context)
  617. {
  618. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  619. {
  620. serializeContext->Class<SubData>()
  621. ->Version(1)
  622. ->Field("SubInt", &SubData::m_int)
  623. ->Field("SubToggle", &SubData::m_bool)
  624. ->Field("SubFloat", &SubData::m_float)
  625. ;
  626. serializeContext->Class<GroupTestComponent, AZ::Component>()
  627. ->Version(1)
  628. ->Field("Float", &GroupTestComponent::m_float)
  629. ->Field("GroupToggle", &GroupTestComponent::m_groupToggle)
  630. ->Field("GroupFloat", &GroupTestComponent::m_groupFloat)
  631. ->Field("ToggleGroupInt", &GroupTestComponent::m_toggleGroupInt)
  632. ->Field("SubDataNormal", &GroupTestComponent::m_subGroupForNormal)
  633. ->Field("SubDataToggle", &GroupTestComponent::m_subGroupForToggle)
  634. ;
  635. if (AZ::EditContext* edit = serializeContext->GetEditContext())
  636. {
  637. edit->Class<GroupTestComponent>("Group Test Component", "Testing normal groups and toggle groups")
  638. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  639. ->DataElement(nullptr, &GroupTestComponent::m_float, "Float Field", "A float field")
  640. ->ClassElement(AZ::Edit::ClassElements::Group, "Normal Group")
  641. ->DataElement(nullptr, &GroupTestComponent::m_groupFloat, "Float Field", "A float field")
  642. ->DataElement(nullptr, &GroupTestComponent::m_subGroupForNormal, "Struct Field", "A sub data type")
  643. ->GroupElementToggle("Group Toggle", &GroupTestComponent::m_groupToggle)
  644. ->DataElement(nullptr, &GroupTestComponent::m_toggleGroupInt, "Normal Integer", "An Integer")
  645. ->DataElement(nullptr, &GroupTestComponent::m_subGroupForToggle, "Struct Field", "A sub data type")
  646. ;
  647. edit->Class<SubData>("SubGroup Test Component", "Testing nested normal groups and toggle groups")
  648. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  649. ->ClassElement(AZ::Edit::ClassElements::Group, "Normal SubGroup")
  650. ->DataElement(nullptr, &SubData::m_int, "SubGroup Int Field", "An int")
  651. ->GroupElementToggle("SubGroup Toggle", &SubData::m_bool)
  652. ->DataElement(nullptr, &SubData::m_float, "SubGroup Float Field", "An int")
  653. ;
  654. }
  655. }
  656. }
  657. void Activate() override
  658. {
  659. }
  660. void Deactivate() override
  661. {
  662. }
  663. float m_float = 0.f;
  664. float m_groupFloat = 0.f;
  665. int m_toggleGroupInt = 0;
  666. AZStd::string m_string;
  667. bool m_groupToggle = false;
  668. SubData m_subGroupForNormal;
  669. SubData m_subGroupForToggle;
  670. };
  671. class InstanceDataHierarchyGroupTestFixture : public LeakDetectionFixture
  672. {
  673. public:
  674. InstanceDataHierarchyGroupTestFixture() = default;
  675. AZStd::unique_ptr<SerializeContext> m_serializeContext;
  676. AZStd::unique_ptr<AZ::Entity> testEntity1;
  677. AzToolsFramework::InstanceDataHierarchy* instanceDataHierarchy;
  678. AzToolsFramework::InstanceDataNode* componentNode1 = nullptr;
  679. void SetUp() override
  680. {
  681. LeakDetectionFixture::SetUp();
  682. using AzToolsFramework::InstanceDataHierarchy;
  683. using AzToolsFramework::InstanceDataNode;
  684. m_serializeContext.reset(aznew AZ::SerializeContext());
  685. m_serializeContext.get()->CreateEditContext();
  686. Entity::Reflect(m_serializeContext.get());
  687. GroupTestComponent::Reflect(m_serializeContext.get());
  688. testEntity1.reset(new AZ::Entity());
  689. testEntity1->CreateComponent<GroupTestComponent>();
  690. instanceDataHierarchy = aznew InstanceDataHierarchy();
  691. instanceDataHierarchy->AddRootInstance(testEntity1.get());
  692. instanceDataHierarchy->Build(m_serializeContext.get(), 0);
  693. // Adding the nodes to a node stack
  694. auto rootNode = instanceDataHierarchy->GetRootNode();
  695. AZStd::stack<InstanceDataNode*> nodeStack;
  696. nodeStack.push(rootNode);
  697. while (!nodeStack.empty())
  698. {
  699. InstanceDataNode* node = nodeStack.top();
  700. nodeStack.pop();
  701. if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<GroupTestComponent>::Uuid())
  702. {
  703. componentNode1 = node;
  704. break;
  705. }
  706. for (InstanceDataNode& child : node->GetChildren())
  707. {
  708. nodeStack.push(&child);
  709. }
  710. }
  711. }
  712. void TearDown() override
  713. {
  714. m_serializeContext.reset();
  715. testEntity1.reset();
  716. delete instanceDataHierarchy;
  717. LeakDetectionFixture::TearDown();
  718. }
  719. };
  720. class InstanceDataHierarchyKeyedContainerTest
  721. : public LeakDetectionFixture
  722. {
  723. public:
  724. class CustomKeyWithoutStringRepresentation
  725. {
  726. public:
  727. AZ_TYPE_INFO(CustomKeyWithoutStringRepresentation, "{54E838DE-1A8D-4BBA-BD3A-D41886C439A9}");
  728. AZ_CLASS_ALLOCATOR(CustomKeyWithoutStringRepresentation, AZ::SystemAllocator);
  729. int m_value = 0;
  730. int operator<(const CustomKeyWithoutStringRepresentation& other) const
  731. {
  732. return m_value < other.m_value;
  733. }
  734. };
  735. class CustomKeyWithStringRepresentation
  736. {
  737. public:
  738. AZ_TYPE_INFO(CustomKeyWithStringRepresentation, "{51F7FB74-2991-4CC9-850A-8D5AA0732282}");
  739. AZ_CLASS_ALLOCATOR(CustomKeyWithStringRepresentation, AZ::SystemAllocator);
  740. static const char* KeyPrefix() { return "CustomKey"; }
  741. int m_value = 0;
  742. int operator<(const CustomKeyWithStringRepresentation& other) const
  743. {
  744. return m_value < other.m_value;
  745. }
  746. AZStd::string ToString() const
  747. {
  748. return AZStd::string::format("%s %i", KeyPrefix(), m_value);
  749. }
  750. };
  751. class KeyedContainer
  752. {
  753. public:
  754. AZ_TYPE_INFO(KeyedContainer, "{53A7416F-2D84-4256-97B0-BE4B6EF6DBAF}");
  755. AZ_CLASS_ALLOCATOR(KeyedContainer, AZ::SystemAllocator);
  756. AZStd::map<AZStd::string, float> m_map;
  757. AZStd::unordered_map<AZStd::pair<int, double>, int> m_unorderedMap;
  758. AZStd::set<int> m_set;
  759. AZStd::unordered_set<AZ::u64> m_unorderedSet;
  760. AZStd::unordered_multimap<int, AZStd::string> m_multiMap;
  761. AZStd::unordered_map<int, AZStd::unordered_map<int, int>> m_nestedMap;
  762. AZStd::map<CustomKeyWithoutStringRepresentation, int> m_uncollapsableMap;
  763. AZStd::map<CustomKeyWithStringRepresentation, int> m_collapsableMap;
  764. static void Reflect(AZ::SerializeContext& context)
  765. {
  766. context.Class<CustomKeyWithoutStringRepresentation>()
  767. ->Field("value", &CustomKeyWithoutStringRepresentation::m_value);
  768. context.Class<CustomKeyWithStringRepresentation>()
  769. ->Field("value", &CustomKeyWithStringRepresentation::m_value);
  770. context.Class<KeyedContainer>()
  771. ->Field("map", &KeyedContainer::m_map)
  772. ->Field("unorderedMap", &KeyedContainer::m_unorderedMap)
  773. ->Field("set", &KeyedContainer::m_set)
  774. ->Field("unorderedSet", &KeyedContainer::m_unorderedSet)
  775. ->Field("multiMap", &KeyedContainer::m_multiMap)
  776. ->Field("nestedMap", &KeyedContainer::m_nestedMap)
  777. ->Field("uncollapsableMap", &KeyedContainer::m_uncollapsableMap)
  778. ->Field("collapsableMap", &KeyedContainer::m_collapsableMap);
  779. if (auto editContext = context.GetEditContext())
  780. {
  781. editContext->Class<CustomKeyWithStringRepresentation>("CustomKeyWithStringRepresentation", "")
  782. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  783. ->Attribute(AZ::Edit::Attributes::ConciseEditorStringRepresentation, &CustomKeyWithStringRepresentation::ToString);
  784. }
  785. }
  786. };
  787. struct KeyTestData
  788. {
  789. virtual void InsertAndVerifyKeys(AZ::SerializeContext::IDataContainer* container, void* key, void* instance, const AZ::SerializeContext::ClassElement* classElement) const = 0;
  790. virtual AZ::Uuid ExpectedKeyType() const = 0;
  791. virtual size_t NumberOfKeys() const = 0;
  792. virtual ~KeyTestData() {}
  793. };
  794. template <class T>
  795. struct TypedKeyTestData : public KeyTestData
  796. {
  797. AZStd::vector<T> keysToInsert;
  798. TypedKeyTestData(std::initializer_list<T> keys)
  799. : keysToInsert(keys)
  800. {
  801. }
  802. void InsertAndVerifyKeys(AZ::SerializeContext::IDataContainer* container, void* key, void* instance, const AZ::SerializeContext::ClassElement* classElement) const override
  803. {
  804. T* keyContainer = reinterpret_cast<T*>(key);
  805. for (const T& keyToInsert : keysToInsert)
  806. {
  807. *keyContainer = keyToInsert;
  808. void* element = container->ReserveElement(instance, classElement);
  809. auto associativeInterface = container->GetAssociativeContainerInterface();
  810. associativeInterface->SetElementKey(element, key);
  811. container->StoreElement(instance, element);
  812. auto lookupKey = associativeInterface->GetElementByKey(instance, classElement, (void*)(&keyToInsert));
  813. EXPECT_NE(lookupKey, nullptr);
  814. }
  815. }
  816. AZ::Uuid ExpectedKeyType() const override
  817. {
  818. return azrtti_typeid<AZ::Internal::RValueToLValueWrapper<T>>();
  819. }
  820. size_t NumberOfKeys() const override
  821. {
  822. return keysToInsert.size();
  823. }
  824. static AZStd::unique_ptr<TypedKeyTestData<T>> Create(std::initializer_list<T> keys)
  825. {
  826. return AZStd::make_unique<TypedKeyTestData<T>>(keys);
  827. }
  828. };
  829. void run()
  830. {
  831. using namespace AzToolsFramework;
  832. AZ::SerializeContext serializeContext;
  833. serializeContext.CreateEditContext();
  834. KeyedContainer::Reflect(serializeContext);
  835. KeyedContainer kc;
  836. InstanceDataHierarchy idh;
  837. idh.AddRootInstance(&kc, azrtti_typeid<KeyedContainer>());
  838. idh.Build(&serializeContext, 0);
  839. AZStd::unordered_map<AZ::u32, AZStd::unique_ptr<KeyTestData>> keyTestData;
  840. keyTestData[AZ_CRC_CE("map")] = TypedKeyTestData<AZStd::string>::Create({"A", "B", "lorem ipsum"});
  841. keyTestData[AZ_CRC_CE("unorderedMap")] = TypedKeyTestData<AZStd::pair<int, double>>::Create({ {5, 1.0}, {5, -2.0} });
  842. keyTestData[AZ_CRC_CE("set")] = TypedKeyTestData<int>::Create({2, 4, -255, 999});
  843. keyTestData[AZ_CRC_CE("unorderedSet")] = TypedKeyTestData<AZ::u64>::Create({500000, 9, 0, 42, 42});
  844. keyTestData[AZ_CRC_CE("multiMap")] = TypedKeyTestData<int>::Create({-1, 2, -3, 4, -5, 6});
  845. keyTestData[AZ_CRC_CE("nestedMap")] = TypedKeyTestData<int>::Create({1, 10, 100, 1000});
  846. keyTestData[AZ_CRC_CE("uncollapsableMap")] = TypedKeyTestData<CustomKeyWithoutStringRepresentation>::Create({{0}, {1}});
  847. keyTestData[AZ_CRC_CE("collapsableMap")] = TypedKeyTestData<CustomKeyWithStringRepresentation>::Create({{0}, {1}});
  848. auto insertKeysIntoContainer = [&serializeContext](AzToolsFramework::InstanceDataNode& node, KeyTestData* keysToInsert)
  849. {
  850. const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
  851. AZ::SerializeContext::IDataContainer* container = node.GetClassMetadata()->m_container;
  852. ASSERT_NE(element, nullptr);
  853. ASSERT_NE(container, nullptr);
  854. const AZ::SerializeContext::ClassElement* containerClassElement = container->GetElement(container->GetDefaultElementNameCrc());
  855. auto associativeInterface = container->GetAssociativeContainerInterface();
  856. ASSERT_NE(associativeInterface, nullptr);
  857. auto key = associativeInterface->CreateKey();
  858. auto attribute = containerClassElement ->FindAttribute(AZ_CRC_CE("KeyType"));
  859. auto attributeData = azrtti_cast<AttributeData<AZ::TypeId>*>(attribute);
  860. ASSERT_NE(attributeData, nullptr);
  861. auto keyId = attributeData->Get(node.FirstInstance());
  862. ASSERT_EQ(keyId, keysToInsert->ExpectedKeyType());
  863. // Ensure we can build an InstanceDataHierarchy at runtime from the container's KeyType
  864. InstanceDataHierarchy idh2;
  865. idh2.AddRootInstance(key.get(), keyId);
  866. idh2.Build(&serializeContext, 0);
  867. auto children = idh2.GetChildren();
  868. EXPECT_EQ(children.size(), 1);
  869. keysToInsert->InsertAndVerifyKeys(container, key.get(), node.FirstInstance(), element);
  870. };
  871. for (InstanceDataNode& node : idh.GetChildren())
  872. {
  873. const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
  874. auto insertIterator = keyTestData.find(element->m_nameCrc);
  875. ASSERT_NE(insertIterator, keyTestData.end());
  876. auto keysToInsert = insertIterator->second.get();
  877. insertKeysIntoContainer(node, keysToInsert);
  878. }
  879. auto nestedKeys = TypedKeyTestData<int>::Create({2, 4, 8, 16});
  880. idh.Build(&serializeContext, 0);
  881. for (InstanceDataNode& node : idh.GetChildren())
  882. {
  883. const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
  884. if (element->m_nameCrc == AZ_CRC_CE("nestedMap"))
  885. {
  886. auto children = node.GetChildren();
  887. // We should have entries for each inserted key in the nested map
  888. EXPECT_EQ(children.size(), keyTestData[AZ_CRC_CE("nestedMap")]->NumberOfKeys());
  889. for (AzToolsFramework::InstanceDataNode& child : children)
  890. {
  891. insertKeysIntoContainer(child.GetChildren().back(), nestedKeys.get());
  892. }
  893. }
  894. else if (element->m_nameCrc == AZ_CRC_CE("collapsableMap"))
  895. {
  896. auto children = node.GetChildren();
  897. EXPECT_GT(children.size(), 0);
  898. for (AzToolsFramework::InstanceDataNode& child : children)
  899. {
  900. // Ensure we're getting keys with the correct prefix based on the ConciseEditorStringRepresentation
  901. AZStd::string name = child.GetElementEditMetadata()->m_name;
  902. EXPECT_NE(name.find(CustomKeyWithStringRepresentation::KeyPrefix()), AZStd::string::npos);
  903. }
  904. }
  905. else if (element->m_nameCrc == AZ_CRC_CE("uncollapsableMap"))
  906. {
  907. auto children = node.GetChildren();
  908. EXPECT_GT(children.size(), 0);
  909. for (AzToolsFramework::InstanceDataNode& child : children)
  910. {
  911. auto keyValueChildren = child.GetChildren();
  912. EXPECT_EQ(keyValueChildren.size(), 2);
  913. auto keyValueChildrenIterator = keyValueChildren.begin();
  914. auto keyNode = *keyValueChildrenIterator;
  915. ++keyValueChildrenIterator;
  916. auto valueNode = *keyValueChildrenIterator;
  917. // Ensure key/value pairs that can't be collapsed get labels based on type
  918. EXPECT_EQ(AZ::Crc32(keyNode.GetElementEditMetadata()->m_name), AZ_CRC_CE("Key<CustomKeyWithoutStringRepresentation>"));
  919. EXPECT_EQ(AZ::Crc32(valueNode.GetElementEditMetadata()->m_name), AZ_CRC_CE("Value<int>"));
  920. }
  921. }
  922. }
  923. // Ensure IgnoreKeyValuePairs is respected
  924. idh.SetBuildFlags(InstanceDataHierarchy::Flags::IgnoreKeyValuePairs);
  925. idh.Build(&serializeContext, 0);
  926. for (InstanceDataNode& node : idh.GetChildren())
  927. {
  928. const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
  929. if (element->m_nameCrc == AZ_CRC_CE("map") || element->m_nameCrc == AZ_CRC_CE("unorderedMap") || element->m_nameCrc == AZ_CRC_CE("nestedMap"))
  930. {
  931. for (InstanceDataNode& pair : node.GetChildren())
  932. {
  933. EXPECT_EQ(pair.GetChildren().size(), 2);
  934. }
  935. }
  936. }
  937. }
  938. };
  939. class InstanceDataHierarchyCompareAssociativeContainerTest
  940. : public LeakDetectionFixture
  941. {
  942. public:
  943. class Container
  944. {
  945. public:
  946. AZ_TYPE_INFO(Container, "{9920B5BD-F21C-4353-9449-9C3FD38E50FC}");
  947. AZ_CLASS_ALLOCATOR(Container, AZ::SystemAllocator);
  948. AZStd::unordered_map<AZStd::string, int> m_map;
  949. static void Reflect(AZ::SerializeContext& context)
  950. {
  951. context.Class<Container>()
  952. ->Field("map", &Container::m_map);
  953. }
  954. };
  955. void run()
  956. {
  957. using namespace AzToolsFramework;
  958. AZ::SerializeContext serializeContext;
  959. Container::Reflect(serializeContext);
  960. Container c1;
  961. c1.m_map = {
  962. {"A", 1},
  963. {"B", 2},
  964. {"C", 3}
  965. };
  966. Container c2;
  967. c2.m_map = {
  968. {"C", 1},
  969. {"A", 2},
  970. {"B", 3}
  971. };
  972. Container c3;
  973. c3.m_map = {
  974. {"A", 2},
  975. {"D", 3}
  976. };
  977. auto testComparison = [&](Container& baseInstance,
  978. Container& compareInstance,
  979. AZStd::unordered_set<AZStd::string> expectedAdds,
  980. AZStd::unordered_set<AZStd::string> expectedRemoves,
  981. AZStd::unordered_set<AZStd::string> expectedChanges)
  982. {
  983. InstanceDataHierarchy idhBase;
  984. idhBase.AddRootInstance(&baseInstance, azrtti_typeid<Container>());
  985. idhBase.Build(&serializeContext, 0);
  986. InstanceDataHierarchy idhCompare;
  987. idhCompare.AddRootInstance(&compareInstance, azrtti_typeid<Container>());
  988. idhCompare.Build(&serializeContext, 0);
  989. AZStd::unordered_set<AZStd::string> actualAdds;
  990. AZStd::unordered_set<AZStd::string> actualRemoves;
  991. AZStd::unordered_set<AZStd::string> actualChanges;
  992. auto newNodeCB = [&](InstanceDataNode* newNode, AZStd::vector<AZ::u8>&)
  993. {
  994. actualAdds.insert(newNode->GetElementEditMetadata()->m_name);
  995. };
  996. auto removedNodeCB = [&](const InstanceDataNode* sourceNode, InstanceDataNode*)
  997. {
  998. actualRemoves.insert(sourceNode->GetElementEditMetadata()->m_name);
  999. };
  1000. auto changedNodeCB = [&](const InstanceDataNode* sourceNode, const InstanceDataNode*, AZStd::vector<AZ::u8>&, AZStd::vector<AZ::u8>&)
  1001. {
  1002. actualChanges.insert(sourceNode->GetParent()->GetElementEditMetadata()->m_name);
  1003. };
  1004. InstanceDataHierarchy::CompareHierarchies(&idhBase,
  1005. &idhCompare,
  1006. &InstanceDataHierarchy::DefaultValueComparisonFunction,
  1007. &serializeContext,
  1008. newNodeCB,
  1009. removedNodeCB,
  1010. changedNodeCB
  1011. );
  1012. EXPECT_EQ(expectedAdds, actualAdds);
  1013. EXPECT_EQ(expectedRemoves, actualRemoves);
  1014. EXPECT_EQ(expectedChanges, actualChanges);
  1015. };
  1016. Container cCopy = c1;
  1017. testComparison(c1, cCopy, {}, {}, {});
  1018. testComparison(c1, c3, {"D", "[0]", "[1]"}, {"B", "C"}, {"A"});
  1019. testComparison(c3, c1, {"B", "C", "[0]", "[1]"}, {"D"}, {"A"});
  1020. testComparison(c1, c2, {}, {}, {"A", "B", "C"});
  1021. }
  1022. };
  1023. class InstanceDataHierarchyElementTest
  1024. : public LeakDetectionFixture
  1025. {
  1026. public:
  1027. class UIElementContainer
  1028. : public AZ::Component
  1029. {
  1030. public:
  1031. AZ_COMPONENT(UIElementContainer, "{83B7BDFD-8B60-4C52-B7C5-BF3C824620F5}", AZ::Component);
  1032. int m_data;
  1033. static void Reflect(AZ::ReflectContext* context)
  1034. {
  1035. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  1036. {
  1037. serialize->Class<UIElementContainer, AZ::Component>()
  1038. ->Field("data", &UIElementContainer::m_data)
  1039. ;
  1040. if (auto editContext = serialize->GetEditContext())
  1041. {
  1042. editContext->Class<UIElementContainer>("Test", "")
  1043. ->UIElement("TestHandler", "UIElement")
  1044. ->DataElement(nullptr, &UIElementContainer::m_data)
  1045. ->UIElement(AZ_CRC_CE("TestHandler2"), "UIElement2")
  1046. ;
  1047. }
  1048. }
  1049. }
  1050. // AZ::Component overrides ...
  1051. void Activate() override {}
  1052. void Deactivate() override {}
  1053. };
  1054. void run()
  1055. {
  1056. using namespace AzToolsFramework;
  1057. AZ::SerializeContext serializeContext;
  1058. serializeContext.CreateEditContext();
  1059. AZ::Entity::Reflect(&serializeContext);
  1060. UIElementContainer::Reflect(&serializeContext);
  1061. UIElementContainer test;
  1062. InstanceDataHierarchy idh;
  1063. idh.AddRootInstance(&test, azrtti_typeid<UIElementContainer>());
  1064. idh.Build(&serializeContext, 0);
  1065. auto children = idh.GetChildren();
  1066. ASSERT_EQ(children.size(), 4);
  1067. auto it = children.begin();
  1068. // The first child will be the AZ::Component Id data that isn't
  1069. // exposed to the EditContext, so it has no edit metadata
  1070. EXPECT_STREQ(it->GetElementMetadata()->m_name, "BaseClass1");
  1071. EXPECT_EQ(it->GetElementEditMetadata(), nullptr);
  1072. ++it;
  1073. Crc32 uiHandler;
  1074. EXPECT_EQ(it->ReadAttribute(AZ::Edit::UIHandlers::Handler, uiHandler), true);
  1075. EXPECT_EQ(uiHandler, AZ_CRC_CE("TestHandler"));
  1076. EXPECT_STREQ(it->GetElementMetadata()->m_name, "UIElement");
  1077. EXPECT_EQ(it->GetElementMetadata()->m_nameCrc, AZ_CRC_CE("UIElement"));
  1078. // The "data" element is in between the two UIElements
  1079. ++it;
  1080. EXPECT_STREQ(it->GetElementMetadata()->m_name, "data");
  1081. ++it;
  1082. uiHandler = Crc32();
  1083. EXPECT_EQ(it->ReadAttribute(AZ::Edit::UIHandlers::Handler, uiHandler), true);
  1084. EXPECT_EQ(uiHandler, AZ_CRC_CE("TestHandler2"));
  1085. EXPECT_STREQ(it->GetElementMetadata()->m_name, "UIElement2");
  1086. EXPECT_EQ(it->GetElementMetadata()->m_nameCrc, AZ_CRC_CE("UIElement2"));
  1087. }
  1088. };
  1089. class InstanceDataHierarchyEndGroupTest
  1090. : public LeakDetectionFixture
  1091. {
  1092. public:
  1093. class EndGroupContainer
  1094. : public AZ::Component
  1095. {
  1096. public:
  1097. AZ_COMPONENT(EndGroupContainer, "{0CFC7838-9B01-4E25-8932-1C1EE4FCEC77}", AZ::Component);
  1098. int m_rootData;
  1099. bool m_otherRootData;
  1100. int m_group1Data;
  1101. int m_group2Data;
  1102. int m_finalRootData;
  1103. static void Reflect(AZ::ReflectContext* context)
  1104. {
  1105. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  1106. {
  1107. serialize->Class<EndGroupContainer, AZ::Component>()
  1108. ->Field("rootData", &EndGroupContainer::m_rootData)
  1109. ->Field("otherRootData", &EndGroupContainer::m_otherRootData)
  1110. ->Field("group1Data", &EndGroupContainer::m_group1Data)
  1111. ->Field("group2Data", &EndGroupContainer::m_group2Data)
  1112. ->Field("finalRootData", &EndGroupContainer::m_finalRootData)
  1113. ;
  1114. if (auto editContext = serialize->GetEditContext())
  1115. {
  1116. editContext->Class<EndGroupContainer>("EndGroupTest", "")
  1117. ->DataElement(nullptr, &EndGroupContainer::m_rootData)
  1118. ->DataElement(nullptr, &EndGroupContainer::m_otherRootData)
  1119. ->ClassElement(AZ::Edit::ClassElements::Group, "First Group")
  1120. ->DataElement(nullptr, &EndGroupContainer::m_group1Data)
  1121. ->ClassElement(AZ::Edit::ClassElements::Group, "Second Group")
  1122. ->DataElement(nullptr, &EndGroupContainer::m_group2Data)
  1123. ->EndGroup()
  1124. ->DataElement(nullptr, &EndGroupContainer::m_finalRootData)
  1125. ;
  1126. }
  1127. }
  1128. }
  1129. // AZ::Component overrides ...
  1130. void Activate() override {}
  1131. void Deactivate() override {}
  1132. };
  1133. void run()
  1134. {
  1135. AZ::SerializeContext serializeContext;
  1136. serializeContext.CreateEditContext();
  1137. AZ::Entity::Reflect(&serializeContext);
  1138. EndGroupContainer::Reflect(&serializeContext);
  1139. EndGroupContainer test;
  1140. AzToolsFramework::InstanceDataHierarchy idh;
  1141. idh.AddRootInstance(&test, azrtti_typeid<EndGroupContainer>());
  1142. idh.Build(&serializeContext, 0);
  1143. auto children = idh.GetChildren();
  1144. ASSERT_EQ(children.size(), 6);
  1145. auto it = children.begin();
  1146. // The first child will be the AZ::Component Id data,
  1147. // which we don't care about for this test
  1148. ++it;
  1149. // The next two children are root data, which both should
  1150. // have no group element
  1151. EXPECT_STREQ(it->GetElementMetadata()->m_name, "rootData");
  1152. EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
  1153. ++it;
  1154. EXPECT_STREQ(it->GetElementMetadata()->m_name, "otherRootData");
  1155. EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
  1156. // The next data should be in the first group
  1157. ++it;
  1158. EXPECT_STREQ(it->GetElementMetadata()->m_name, "group1Data");
  1159. EXPECT_STREQ(it->GetGroupElementMetadata()->m_description, "First Group");
  1160. // The following data should be in the second group
  1161. ++it;
  1162. EXPECT_STREQ(it->GetElementMetadata()->m_name, "group2Data");
  1163. EXPECT_STREQ(it->GetGroupElementMetadata()->m_description, "Second Group");
  1164. // The final data should have no group since we ended the second group
  1165. ++it;
  1166. EXPECT_STREQ(it->GetElementMetadata()->m_name, "finalRootData");
  1167. EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
  1168. }
  1169. };
  1170. class InstanceDataHierarchyAggregateInstanceTest
  1171. : public LeakDetectionFixture
  1172. {
  1173. public:
  1174. class AggregatedContainer
  1175. {
  1176. public:
  1177. AZ_TYPE_INFO(AggregatedContainer, "{42E09F38-2D26-4FED-9901-06003A030ED5}");
  1178. AZ_CLASS_ALLOCATOR(AggregatedContainer, AZ::SystemAllocator);
  1179. int m_aggregated;
  1180. int m_notAggregated;
  1181. static void Reflect(AZ::SerializeContext& context)
  1182. {
  1183. context.Class<AggregatedContainer>()
  1184. ->Field("aggregatedDataElement", &AggregatedContainer::m_aggregated)
  1185. ->Field("notAggregatedDataElement", &AggregatedContainer::m_notAggregated)
  1186. ;
  1187. if (auto editContext = context.GetEditContext())
  1188. {
  1189. // By default, DataElements accept multi-edit and UIElements do not
  1190. editContext->Class<AggregatedContainer>("Test", "")
  1191. ->DataElement(nullptr, &AggregatedContainer::m_aggregated)
  1192. ->DataElement(nullptr, &AggregatedContainer::m_notAggregated)
  1193. ->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, false)
  1194. ->UIElement("TestHandler", "aggregatedUIElement")
  1195. ->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, true)
  1196. ->UIElement(AZ_CRC_CE("TestHandler2"), "notAggregatedUIElement")
  1197. ;
  1198. }
  1199. }
  1200. };
  1201. void run()
  1202. {
  1203. using namespace AzToolsFramework;
  1204. AZ::SerializeContext serializeContext;
  1205. serializeContext.CreateEditContext();
  1206. AggregatedContainer::Reflect(serializeContext);
  1207. InstanceDataHierarchy idh;
  1208. AZStd::list<AggregatedContainer> containers;
  1209. for (int i = 0; i < 5; ++i)
  1210. {
  1211. AggregatedContainer& container = containers.emplace_back();
  1212. idh.AddRootInstance(&container, azrtti_typeid<AggregatedContainer>());
  1213. idh.Build(&serializeContext, 0);
  1214. auto children = idh.GetChildren();
  1215. // If we have multiple instances, the two non-aggregating elements should go away
  1216. ASSERT_EQ(children.size(), i == 0 ? 4 : 2);
  1217. auto it = children.begin();
  1218. EXPECT_STREQ(it->GetElementMetadata()->m_name, "aggregatedDataElement");
  1219. ++it;
  1220. if (i == 0)
  1221. {
  1222. EXPECT_STREQ(it->GetElementMetadata()->m_name, "notAggregatedDataElement");
  1223. ++it;
  1224. }
  1225. EXPECT_STREQ(it->GetElementMetadata()->m_name, "aggregatedUIElement");
  1226. ++it;
  1227. if (i == 0)
  1228. {
  1229. EXPECT_STREQ(it->GetElementMetadata()->m_name, "notAggregatedUIElement");
  1230. ++it;
  1231. }
  1232. }
  1233. }
  1234. };
  1235. TEST_F(InstanceDataHierarchyBasicTest, Test)
  1236. {
  1237. run();
  1238. }
  1239. TEST_F(InstanceDataHierarchyCopyContainerChangesTest, Test)
  1240. {
  1241. run();
  1242. }
  1243. TEST_F(InstanceDataHierarchyEnumContainerTest, Test)
  1244. {
  1245. run();
  1246. }
  1247. TEST_F(InstanceDataHierarchyKeyedContainerTest, Test)
  1248. {
  1249. run();
  1250. }
  1251. TEST_F(InstanceDataHierarchyKeyedContainerTest, RemovingMultipleItemsFromContainerDoesNotCrash)
  1252. {
  1253. using TestMap = AZStd::unordered_map<double, double>;
  1254. TestMap testMap;
  1255. AZStd::initializer_list<AZStd::pair<double, double>> valuesToInsert{ {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0} };
  1256. AZ::GenericClassInfo* mapGenericClassInfo = AZ::SerializeGenericTypeInfo<TestMap>::GetGenericInfo();
  1257. AZ::SerializeContext::ClassData* mapClassData = mapGenericClassInfo->GetClassData();
  1258. ASSERT_NE(nullptr, mapClassData);
  1259. AZ::SerializeContext::IDataContainer* mapDataContainer = mapClassData->m_container;
  1260. ASSERT_NE(nullptr, mapDataContainer);
  1261. auto associativeInterface = mapDataContainer->GetAssociativeContainerInterface();
  1262. AZ::SerializeContext::ClassElement classElement;
  1263. AZ::SerializeContext::DataElement dataElement;
  1264. dataElement.m_nameCrc = mapDataContainer->GetDefaultElementNameCrc();
  1265. EXPECT_TRUE(mapDataContainer->GetElement(classElement, dataElement));
  1266. AZStd::vector<double> keyRemovalContainer;
  1267. keyRemovalContainer.reserve(valuesToInsert.size());
  1268. for (const AZStd::pair<double, double>& valueToInsert : valuesToInsert)
  1269. {
  1270. void* newElement = mapDataContainer->ReserveElement(&testMap, &classElement);
  1271. *reinterpret_cast<typename TestMap::value_type*>(newElement) = valueToInsert;
  1272. mapDataContainer->StoreElement(&testMap, newElement);
  1273. keyRemovalContainer.push_back(valueToInsert.first);
  1274. }
  1275. EXPECT_EQ(valuesToInsert.size(), testMap.size());
  1276. for (const AZStd::pair<double, double>& testValue : valuesToInsert)
  1277. {
  1278. // Make sure all elements within initializer_list is in the map
  1279. void* lookupValue = associativeInterface->GetElementByKey(&testMap, &classElement, &testValue.first);
  1280. EXPECT_NE(nullptr, lookupValue);
  1281. }
  1282. // Shuffle the keys around and attempt to remove the keys using IDataContainer::RemoveElement
  1283. SerializeContext serializeContext;
  1284. const uint32_t rngSeed = std::random_device{}();
  1285. std::mt19937 mtTwisterRng(rngSeed);
  1286. std::shuffle(keyRemovalContainer.begin(), keyRemovalContainer.end(), mtTwisterRng);
  1287. for (double key : keyRemovalContainer)
  1288. {
  1289. void* valueToRemove = associativeInterface->GetElementByKey(&testMap, &classElement, &key);
  1290. EXPECT_TRUE(mapDataContainer->RemoveElement(&testMap, valueToRemove, &serializeContext));
  1291. }
  1292. EXPECT_EQ(0, mapDataContainer->Size(&testMap));
  1293. }
  1294. TEST_F(InstanceDataHierarchyCompareAssociativeContainerTest, TestComparingAssociativeContainers)
  1295. {
  1296. run();
  1297. }
  1298. TEST_F(InstanceDataHierarchyElementTest, TestLayingOutUIAndDataElements)
  1299. {
  1300. run();
  1301. }
  1302. TEST_F(InstanceDataHierarchyEndGroupTest, TestEndGroup)
  1303. {
  1304. run();
  1305. }
  1306. TEST_F(InstanceDataHierarchyAggregateInstanceTest, TestRespectingAggregateInstanceVisibility)
  1307. {
  1308. run();
  1309. }
  1310. // Test to validate that the only ClassElement::Group nodes are ToggleGroups
  1311. TEST_F(InstanceDataHierarchyGroupTestFixture, GroupToggleIsClassElementGroup)
  1312. {
  1313. using AzToolsFramework::InstanceDataHierarchy;
  1314. using AzToolsFramework::InstanceDataNode;
  1315. for (auto child : componentNode1->GetChildren())
  1316. {
  1317. AZStd::string childName(child.GetElementMetadata()->m_name);
  1318. if (childName.compare("GroupToggle") == 0)
  1319. {
  1320. EXPECT_EQ(child.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
  1321. }
  1322. if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
  1323. {
  1324. for (auto subChild : child.GetChildren())
  1325. {
  1326. childName = subChild.GetElementMetadata()->m_name;
  1327. if (childName.compare("SubToggle") == 0)
  1328. {
  1329. EXPECT_EQ(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
  1330. }
  1331. else
  1332. {
  1333. EXPECT_NE(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
  1334. }
  1335. }
  1336. }
  1337. }
  1338. }
  1339. // Test to ensure that each node has been assigned under the proper group and the group hierarchy is structured correctly
  1340. TEST_F(InstanceDataHierarchyGroupTestFixture, ValidatingGroupAndSubGroupHierarchy)
  1341. {
  1342. using AzToolsFramework::InstanceDataHierarchy;
  1343. using AzToolsFramework::InstanceDataNode;
  1344. for (auto child : componentNode1->GetChildren())
  1345. {
  1346. AZStd::string childName(child.GetElementMetadata()->m_name);
  1347. if (childName.compare("GroupFloat") == 0)
  1348. {
  1349. EXPECT_STREQ(child.GetGroupElementMetadata()->m_description, "Normal Group");
  1350. }
  1351. if (childName.compare("ToggleGroupInt") == 0)
  1352. {
  1353. EXPECT_STREQ(child.GetGroupElementMetadata()->m_description, "Group Toggle");
  1354. }
  1355. if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
  1356. {
  1357. for (auto subChild : child.GetChildren())
  1358. {
  1359. childName = subChild.GetElementMetadata()->m_name;
  1360. if (childName.compare("SubInt") == 0)
  1361. {
  1362. EXPECT_STREQ(subChild.GetGroupElementMetadata()->m_description, "Normal SubGroup");
  1363. }
  1364. if (childName.compare("SubFloat") == 0)
  1365. {
  1366. EXPECT_STREQ(subChild.GetGroupElementMetadata()->m_description, "SubGroup Toggle");
  1367. }
  1368. }
  1369. }
  1370. }
  1371. }
  1372. class InstanceDataHierarchyGroupTestFixtureParameterized
  1373. : public InstanceDataHierarchyGroupTestFixture
  1374. , public ::testing::WithParamInterface<const char*>
  1375. {
  1376. };
  1377. INSTANTIATE_TEST_SUITE_P(
  1378. InstanceDataHierarchyGroupTestFixture,
  1379. InstanceDataHierarchyGroupTestFixtureParameterized,
  1380. ::testing::Values("GroupFloat", "GroupToggle", "ToggleGroupInt", "SubInt", "SubToggle", "SubFloat"));
  1381. // Test to validate that each node in a group and Subgroup has the correct parent
  1382. TEST_P(InstanceDataHierarchyGroupTestFixtureParameterized, ValidatingGroupAndSubGroupParents)
  1383. {
  1384. using AzToolsFramework::InstanceDataHierarchy;
  1385. using AzToolsFramework::InstanceDataNode;
  1386. const char* paramName = GetParam();
  1387. for (auto child : componentNode1->GetChildren())
  1388. {
  1389. AZStd::string childName(child.GetElementMetadata()->m_name);
  1390. if (childName.compare(paramName) == 0)
  1391. {
  1392. EXPECT_STREQ(child.GetParent()->GetClassMetadata()->m_name, "GroupTestComponent");
  1393. }
  1394. if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
  1395. {
  1396. for (auto subChild : child.GetChildren())
  1397. {
  1398. childName = subChild.GetElementMetadata()->m_name;
  1399. if (childName.compare(paramName) == 0)
  1400. {
  1401. EXPECT_STREQ(subChild.GetParent()->GetClassMetadata()->m_name, "SubData");
  1402. }
  1403. }
  1404. }
  1405. }
  1406. }
  1407. } // namespace UnitTest