12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <AzCore/Component/Entity.h>
- #include <AzCore/Component/Component.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/std/containers/stack.h>
- #include <AzCore/std/containers/map.h>
- #include <AzCore/std/containers/unordered_map.h>
- #include <AzCore/std/containers/set.h>
- #include <AzCore/std/containers/unordered_set.h>
- #include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
- #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
- #include <AzCore/Serialization/ObjectStream.h>
- #include <AzCore/Serialization/Utils.h>
- #include <AzCore/UnitTest/TestTypes.h>
- #include <random>
- #include <QDebug>
- using namespace AZ;
- using namespace AZ::IO;
- using namespace AZ::Debug;
- namespace UnitTest
- {
- class TestComponent
- : public AZ::Component
- {
- public:
- AZ_COMPONENT(TestComponent, "{94D5C952-FD65-4997-B517-F36003F8018A}");
- struct SubData
- {
- AZ_TYPE_INFO(SubData, "{A0165FCA-A311-4FED-B36A-DC5FD2AF2857}");
- AZ_CLASS_ALLOCATOR(SubData, AZ::SystemAllocator);
- SubData() {}
- SubData(int v) : m_int(v) {}
- ~SubData() = default;
- int m_int = 0;
- };
- class SerializationEvents
- : public AZ::SerializeContext::IEventHandler
- {
- void OnReadBegin(void* classPtr) override
- {
- TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
- component->m_serializeOnReadBegin++;
- }
- void OnReadEnd(void* classPtr) override
- {
- TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
- component->m_serializeOnReadEnd++;
- }
- void OnWriteBegin(void* classPtr) override
- {
- TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
- component->m_serializeOnWriteBegin++;
- }
- void OnWriteEnd(void* classPtr) override
- {
- TestComponent* component = reinterpret_cast<TestComponent*>(classPtr);
- component->m_serializeOnWriteEnd++;
- }
- };
- TestComponent() = default;
- ~TestComponent() override
- {
- for (SubData* data : m_pointerContainer)
- {
- delete data;
- }
- }
- static void Reflect(AZ::ReflectContext* context)
- {
- if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serializeContext->Class<SubData>()
- ->Version(1)
- ->Field("Int", &SubData::m_int)
- ;
- serializeContext->Class<TestComponent, AZ::Component>()
- ->EventHandler<SerializationEvents>()
- ->Version(1)
- ->Field("Float", &TestComponent::m_float)
- ->Field("String", &TestComponent::m_string)
- ->Field("NormalContainer", &TestComponent::m_normalContainer)
- ->Field("PointerContainer", &TestComponent::m_pointerContainer)
- ->Field("SubData", &TestComponent::m_subData)
- ;
- if (AZ::EditContext* edit = serializeContext->GetEditContext())
- {
- edit->Class<TestComponent>("Test Component", "A test component")
- ->DataElement(nullptr, &TestComponent::m_float, "Float Field", "A float field")
- ->DataElement(nullptr, &TestComponent::m_string, "String Field", "A string field")
- ->DataElement(nullptr, &TestComponent::m_normalContainer, "Normal Container", "A container")
- ->DataElement(nullptr, &TestComponent::m_pointerContainer, "Pointer Container", "A container")
- ->DataElement(nullptr, &TestComponent::m_subData, "Struct Field", "A sub data type")
- ;
- edit->Class<SubData>("Test Component", "A test component")
- ->DataElement(nullptr, &SubData::m_int, "Int Field", "An int")
- ;
- }
- }
- }
- void Activate() override
- {
- }
- void Deactivate() override
- {
- }
- float m_float = 0.f;
- AZStd::string m_string;
- AZStd::vector<SubData> m_normalContainer;
- AZStd::vector<SubData*> m_pointerContainer;
- SubData m_subData;
- size_t m_serializeOnReadBegin = 0;
- size_t m_serializeOnReadEnd = 0;
- size_t m_serializeOnWriteBegin = 0;
- size_t m_serializeOnWriteEnd = 0;
- };
-
- bool operator==(const TestComponent::SubData& lhs, const TestComponent::SubData& rhs)
- {
- return lhs.m_int == rhs.m_int;
- }
- /**
- * InstanceDataHierarchyBasicTest
- */
- class InstanceDataHierarchyBasicTest
- : public LeakDetectionFixture
- {
- public:
- InstanceDataHierarchyBasicTest()
- {
- }
- ~InstanceDataHierarchyBasicTest() override
- {
- }
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- Entity::Reflect(&serializeContext);
- TestComponent::Reflect(&serializeContext);
- // Test building of hierarchies, and copying of data from testEntity1 to testEntity2->
- {
- AZStd::unique_ptr<AZ::Entity> testEntity1(new AZ::Entity());
- testEntity1->CreateComponent<TestComponent>();
- AZStd::unique_ptr<AZ::Entity> testEntity2(serializeContext.CloneObject(testEntity1.get()));
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 1);
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 1);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteBegin == 1);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteEnd == 1);
- testEntity1->FindComponent<TestComponent>()->m_float = 1.f;
- testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
- testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(2));
- testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
- testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(2));
- // First entity has more entries, so we'll be adding elements to testEntity2->
- testEntity2->FindComponent<TestComponent>()->m_float = 2.f;
- testEntity2->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
- testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
- InstanceDataHierarchy idh1;
- idh1.AddRootInstance(testEntity1.get());
- idh1.Build(&serializeContext, 0);
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 2);
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 2);
- InstanceDataHierarchy idh2;
- idh2.AddRootInstance(testEntity2.get());
- idh2.Build(&serializeContext, 0);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnReadBegin == 1);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnReadEnd == 1);
- // Verify IDH structure.
- InstanceDataNode* root1 = idh1.GetRootNode();
- AZ_TEST_ASSERT(root1);
- InstanceDataNode* root2 = idh2.GetRootNode();
- AZ_TEST_ASSERT(root2);
- auto secondChildIter = root1->GetChildren().begin();
- AZStd::advance(secondChildIter, 1);
- InstanceDataNode::Address addr = secondChildIter->ComputeAddress();
- AZ_TEST_ASSERT(!addr.empty());
- InstanceDataNode* foundIn2 = idh2.FindNodeByAddress(addr);
- AZ_TEST_ASSERT(foundIn2);
- // Find the TestComponent in entity1's IDH.
- AZStd::stack<InstanceDataNode*> nodeStack;
- nodeStack.push(root1);
- InstanceDataNode* componentNode1 = nullptr;
- while (!nodeStack.empty())
- {
- InstanceDataNode* node = nodeStack.top();
- nodeStack.pop();
- if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<TestComponent>::Uuid())
- {
- componentNode1 = node;
- break;
- }
- for (InstanceDataNode& child : node->GetChildren())
- {
- nodeStack.push(&child);
- }
- }
- // Verify we found the component node in both hierarchies.
- AZ_TEST_ASSERT(componentNode1);
- addr = componentNode1->ComputeAddress();
- foundIn2 = idh2.FindNodeByAddress(addr);
- AZ_TEST_ASSERT(foundIn2);
- //// Try copying data from entity 1 to entity 2.
- bool result = InstanceDataHierarchy::CopyInstanceData(componentNode1, foundIn2, &serializeContext);
- AZ_TEST_ASSERT(result);
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadBegin == 2);
- AZ_TEST_ASSERT(testEntity1->FindComponent<TestComponent>()->m_serializeOnReadEnd == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteBegin == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_serializeOnWriteEnd == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_normalContainer.size() == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_pointerContainer.size() == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_float == 1.f);
- }
- // Test removal of container elements during instance data copying.
- {
- AZStd::unique_ptr<AZ::Entity> testEntity1(new AZ::Entity());
- testEntity1->CreateComponent<TestComponent>();
- AZStd::unique_ptr<AZ::Entity> testEntity2(serializeContext.CloneObject(testEntity1.get()));
- // First entity has more in container 1, fewer in container 2 as compared to second entity.
- testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
- testEntity1->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(2));
- testEntity1->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
- testEntity2->FindComponent<TestComponent>()->m_normalContainer.push_back(TestComponent::SubData(1));
- testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(1));
- testEntity2->FindComponent<TestComponent>()->m_pointerContainer.push_back(aznew TestComponent::SubData(2));
- // Change a field.
- testEntity2->FindComponent<TestComponent>()->m_float = 2.f;
- InstanceDataHierarchy idh1;
- idh1.AddRootInstance(testEntity1.get());
- idh1.Build(&serializeContext, 0);
- InstanceDataHierarchy idh2;
- idh2.AddRootInstance(testEntity2.get());
- idh2.Build(&serializeContext, 0);
- InstanceDataNode* root1 = idh1.GetRootNode();
- // Find the TestComponent in entity1's IDH.
- AZStd::stack<InstanceDataNode*> nodeStack;
- nodeStack.push(root1);
- InstanceDataNode* componentNode1 = nullptr;
- while (!nodeStack.empty())
- {
- InstanceDataNode* node = nodeStack.top();
- nodeStack.pop();
- if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<TestComponent>::Uuid())
- {
- componentNode1 = node;
- break;
- }
- for (InstanceDataNode& child : node->GetChildren())
- {
- nodeStack.push(&child);
- }
- }
- // Verify we found the component node in both hierarchies.
- AZ_TEST_ASSERT(componentNode1);
- InstanceDataNode::Address addr = componentNode1->ComputeAddress();
- InstanceDataNode* foundIn2 = idh2.FindNodeByAddress(addr);
- AZ_TEST_ASSERT(foundIn2);
- // Do a comparison test
- {
- size_t newNodes = 0;
- size_t removedNodes = 0;
- size_t changedNodes = 0;
- InstanceDataHierarchy::CompareHierarchies(componentNode1, foundIn2,
- &InstanceDataHierarchy::DefaultValueComparisonFunction,
- &serializeContext,
- // New node
- [&](InstanceDataNode* targetNode, AZStd::vector<AZ::u8>& data)
- {
- (void)targetNode;
- (void)data;
- ++newNodes;
- },
- // Removed node (container element).
- [&](const InstanceDataNode* sourceNode, InstanceDataNode* targetNodeParent)
- {
- (void)sourceNode;
- (void)targetNodeParent;
- ++removedNodes;
- },
- // Changed node
- [&](const InstanceDataNode* sourceNode, InstanceDataNode* targetNode,
- AZStd::vector<AZ::u8>& sourceData, AZStd::vector<AZ::u8>& targetData)
- {
- (void)sourceNode;
- (void)targetNode;
- (void)sourceData;
- (void)targetData;
- ++changedNodes;
- }
- );
- AZ_TEST_ASSERT(newNodes == 2); // 2 because child nodes of new nodes are now also flagged as new
- AZ_TEST_ASSERT(removedNodes == 1);
- AZ_TEST_ASSERT(changedNodes == 1);
- }
- //// Try copying data from entity 1 to entity 2.
- bool result = InstanceDataHierarchy::CopyInstanceData(componentNode1, foundIn2, &serializeContext);
- AZ_TEST_ASSERT(result);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_normalContainer.size() == 2);
- AZ_TEST_ASSERT(testEntity2->FindComponent<TestComponent>()->m_pointerContainer.size() == 1);
- }
- // Test FindNodeByPartialAddress functionality and Read/Write of InstanceDataNode
- {
- const AZStd::string testString = "this is a test";
- const float testFloat = 123.0f;
- const int testInt = 7;
- const TestComponent::SubData testSubData(testInt);
- const AZStd::vector<TestComponent::SubData> testNormalContainer{ TestComponent::SubData(1), TestComponent::SubData(2), TestComponent::SubData(3) };
- // create a test component with some initial values
- AZStd::unique_ptr<TestComponent> testComponent(new TestComponent);
- testComponent.get()->m_float = testFloat;
- testComponent.get()->m_string = testString;
- testComponent.get()->m_normalContainer = testNormalContainer;
- testComponent.get()->m_subData.m_int = testInt;
- // create an InstanceDataHierarchy for the test component
- InstanceDataHierarchy idhTestComponent;
- idhTestComponent.AddRootInstance(testComponent.get());
- idhTestComponent.Build(&serializeContext, 0);
- // create some partial addresses to search for fields in InstanceDataHierarchy
- // note: reflection serialization context values are used for lookup (crcs stored)
- // if a more specific address is required, start from field and work up to structures/components etc
- // (see addrSubDataInt below as an example)
- InstanceDataNode::Address addrFloat = { AZ_CRC_CE("Float") };
- InstanceDataNode::Address addrString = { AZ_CRC_CE("String") };
- InstanceDataNode::Address addrNormalContainer = { AZ_CRC_CE("NormalContainer") };
- InstanceDataNode::Address addrSubData = { AZ_CRC_CE("SubData") };
- InstanceDataNode::Address addrSubDataInt = { AZ_CRC_CE("Int"), AZ_CRC_CE("SubData") };
- // find InstanceDataNodes using partial address
- InstanceDataNode* foundFloat = idhTestComponent.FindNodeByPartialAddress(addrFloat);
- InstanceDataNode* foundString = idhTestComponent.FindNodeByPartialAddress(addrString);
- InstanceDataNode* foundNormalContainer = idhTestComponent.FindNodeByPartialAddress(addrNormalContainer);
- InstanceDataNode* foundSubData = idhTestComponent.FindNodeByPartialAddress(addrSubData);
- InstanceDataNode* foundSubDataInt = idhTestComponent.FindNodeByPartialAddress(addrSubDataInt);
- // ensure each has been returned successfully
- AZ_TEST_ASSERT(foundFloat);
- AZ_TEST_ASSERT(foundString);
- AZ_TEST_ASSERT(foundNormalContainer);
- AZ_TEST_ASSERT(foundSubData);
- AZ_TEST_ASSERT(foundSubDataInt);
- // check a case where we know the address is incorrect and we will not find an InstanceDataNode
- InstanceDataNode::Address addrInvalid = { AZ_CRC_CE("INVALID") };
- InstanceDataNode* foundInvalid = idhTestComponent.FindNodeByPartialAddress(addrInvalid);
- AZ_TEST_ASSERT(foundInvalid == nullptr);
- ///////////////////////////////////////////////////////////////////////////////
- // test the values read from the InstanceDataNodes are the same as the ones our TestComponent were constructed with
- float readTestFloat;
- foundFloat->Read(readTestFloat);
- AZ_TEST_ASSERT(readTestFloat == testFloat);
- AZStd::string readTestString;
- foundString->Read(readTestString);
- AZ_TEST_ASSERT(readTestString == testString);
- int readTestInt;
- foundSubDataInt->Read(readTestInt);
- AZ_TEST_ASSERT(readTestInt == testInt);
- TestComponent::SubData readTestSubData;
- foundSubData->Read(readTestSubData);
- AZ_TEST_ASSERT(readTestSubData == testSubData);
- AZStd::vector<TestComponent::SubData> readTestNormalContainer;
- foundNormalContainer->Read(readTestNormalContainer);
- AZ_TEST_ASSERT(readTestNormalContainer == testNormalContainer);
- // create some new test values to write to the InstanceDataNode
- const AZStd::string newTestString = "this string has been updated!";
- const float newTestFloat = 456.0f;
- const int newTestInt = 94;
- const TestComponent::SubData newTestSubData(newTestInt);
- const AZStd::vector<TestComponent::SubData> newTestNormalContainer{ TestComponent::SubData(20), TestComponent::SubData(40), TestComponent::SubData(60) };
- // actually write the values to each InstanceDataNode
- foundFloat->Write(newTestFloat);
- foundString->Write(newTestString);
- foundSubData->Write(newTestSubData);
- foundNormalContainer->Write(newTestNormalContainer);
- // read the values back to make sure the are the same as the newly set values
- AZStd::string updatedTestString;
- foundString->Read(updatedTestString);
- AZ_TEST_ASSERT(updatedTestString == newTestString);
- float updatedTestFloat;
- foundFloat->Read(updatedTestFloat);
- AZ_TEST_ASSERT(updatedTestFloat == newTestFloat);
- TestComponent::SubData updatedTestSubData;
- foundSubData->Read(updatedTestSubData);
- AZ_TEST_ASSERT(updatedTestSubData == newTestSubData);
- AZStd::vector<TestComponent::SubData> updatedNormalContainer;
- foundNormalContainer->Read(updatedNormalContainer);
- AZ_TEST_ASSERT(updatedNormalContainer == newTestNormalContainer);
- }
- }
- };
- static AZ::u8 s_persistentIdCounter = 0;
- class InstanceDataHierarchyCopyContainerChangesTest
- : public LeakDetectionFixture
- {
- public:
- InstanceDataHierarchyCopyContainerChangesTest()
- {
- }
- ~InstanceDataHierarchyCopyContainerChangesTest() override
- {
- }
- class StructInner
- {
- public:
- AZ_TYPE_INFO(StructInner, "{4BFA2A4F-8568-43AA-941C-8361DBA13CBB}");
- AZ::u8 m_persistentId;
- AZ::u32 m_value;
- StructInner()
- {
- m_value = 1;
- m_persistentId = ++s_persistentIdCounter;
- }
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<StructInner>()->
- PersistentId([](const void* instance) -> u64 { return static_cast<u64>(reinterpret_cast<const StructInner*>(instance)->m_persistentId); })->
- Field("Id", &StructInner::m_persistentId)->
- Field("Value", &StructInner::m_value)
- ;
- }
- };
- class StructOuter
- {
- public:
- AZ_TYPE_INFO(StructOuter, "{FEDCED26-8D5A-41CB-BA97-AB687CF51FC6}");
- AZStd::vector<StructInner> m_vector;
- StructOuter()
- {
- }
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<StructOuter>()->
- Field("Vector", &StructOuter::m_vector)
- ;
- }
- };
- void DoCopy(StructOuter& source, StructOuter& target, AZ::SerializeContext& ctx)
- {
- AzToolsFramework::InstanceDataHierarchy sourceHier;
- sourceHier.AddRootInstance(&source, AZ::AzTypeInfo<StructOuter>::Uuid());
- sourceHier.Build(&ctx, AZ::SerializeContext::ENUM_ACCESS_FOR_READ);
- AzToolsFramework::InstanceDataHierarchy targetHier;
- targetHier.AddRootInstance(&target, AZ::AzTypeInfo<StructOuter>::Uuid());
- targetHier.Build(&ctx, AZ::SerializeContext::ENUM_ACCESS_FOR_READ);
- AzToolsFramework::InstanceDataHierarchy::CopyInstanceData(&sourceHier, &targetHier, &ctx);
- }
- void VerifyMatch(StructOuter& source, StructOuter& target)
- {
- AZ_TEST_ASSERT(source.m_vector.size() == target.m_vector.size());
- // Make sure that matching elements have the same data (we're using persistent Ids, so order can be whatever).
- for (auto& sourceElement : source.m_vector)
- {
- for (auto& targetElement : target.m_vector)
- {
- if (targetElement.m_persistentId == sourceElement.m_persistentId)
- {
- AZ_TEST_ASSERT(targetElement.m_value == sourceElement.m_value);
- break;
- }
- }
- }
- }
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- StructInner::Reflect(serializeContext);
- StructOuter::Reflect(serializeContext);
- StructOuter outerSource;
- StructOuter outerTarget;
- StructOuter originalSource;
- originalSource.m_vector.emplace_back();
- originalSource.m_vector.emplace_back();
- originalSource.m_vector.emplace_back();
- {
- outerSource = originalSource;
- DoCopy(outerSource, outerTarget, serializeContext);
- AZ_TEST_ASSERT(outerTarget.m_vector.size() == 3);
- }
- {
- outerSource = originalSource;
- outerTarget = outerSource;
- // Pluck from the start of the array so elements get shifted.
- // Also modify something in the last element so it's written to the target.
- // This verifies that removals are applied safely alongside data changes.
- outerSource.m_vector.erase(outerSource.m_vector.begin());
- outerSource.m_vector.begin()->m_value = 2;
- DoCopy(outerSource, outerTarget, serializeContext);
- VerifyMatch(outerSource, outerTarget);
- }
- {
- outerSource = originalSource;
- outerTarget = outerSource;
- // Remove an element from the target and SHRINK the array to fit so it's
- // guaranteed to grow when the missing element is copied from the source.
- // This verifies that additions are being applied safely alongside data changes.
- outerTarget.m_vector.erase(outerTarget.m_vector.begin());
- outerTarget.m_vector.set_capacity(outerTarget.m_vector.size()); // Force grow on insert
- outerSource.m_vector.back().m_value = 5;
- DoCopy(outerSource, outerTarget, serializeContext);
- VerifyMatch(outerSource, outerTarget);
- }
- {
- outerSource = originalSource;
- outerTarget = outerSource;
- // Add elements to the source.
- // Add an element to the target.
- // Change a different element.
- // This tests removals, additions, and changes occurring together, with net growth in the target container.
- outerSource.m_vector.emplace_back();
- outerSource.m_vector.emplace_back();
- outerTarget.m_vector.emplace_back();
- outerTarget.m_vector.set_capacity(outerTarget.m_vector.size()); // Force grow on insert
- outerTarget.m_vector.begin()->m_value = 10;
- DoCopy(outerSource, outerTarget, serializeContext);
- VerifyMatch(outerSource, outerTarget);
- }
- }
- };
- enum class TestEnum
- {
- Value1 = 0x01,
- Value2 = 0x02,
- Value3 = 0xFF,
- };
- }
- namespace AZ
- {
- AZ_TYPE_INFO_SPECIALIZE(UnitTest::TestEnum, "{52DBDCC6-0829-4602-A650-E6FC32AFC5F2}");
- }
- namespace UnitTest
- {
- class InstanceDataHierarchyEnumContainerTest
- : public LeakDetectionFixture
- {
- public:
- class EnumContainer
- {
- public:
- AZ_TYPE_INFO(EnumContainer, "{7F9EED53-7587-4616-B4A7-10B3AF95475E}");
- AZ_CLASS_ALLOCATOR(EnumContainer, AZ::SystemAllocator);
- TestEnum m_enum;
- AZStd::vector<TestEnum> m_enumVector;
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<EnumContainer>()
- ->Field("Enum", &EnumContainer::m_enum)
- ->Field("EnumVector", &EnumContainer::m_enumVector)
- ;
- if (EditContext* edit = context.GetEditContext())
- {
- edit->Enum<UnitTest::TestEnum>("TestEnum", "No Description")
- ->Value("Value1", UnitTest::TestEnum::Value1)
- ->Value("Value2", UnitTest::TestEnum::Value2)
- ->Value("Value3", UnitTest::TestEnum::Value3)
- ;
- edit->Class<EnumContainer>("Enum Container", "Test container that has an external enum")
- ->DataElement(nullptr, &EnumContainer::m_enum, "Enum Field", "An enum value")
- ->DataElement(nullptr, &EnumContainer::m_enumVector, "Enum Vector Field", "A vector of enum values")
- ;
- }
- }
- };
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- EnumContainer::Reflect(serializeContext);
- EnumContainer ec;
- ec.m_enumVector.emplace_back(UnitTest::TestEnum::Value3);
- InstanceDataHierarchy idh;
- idh.AddRootInstance(&ec, azrtti_typeid<EnumContainer>());
- idh.Build(&serializeContext, 0);
- InstanceDataNode* enumNode = idh.FindNodeByPartialAddress({ AZ_CRC_CE("Enum") });
- InstanceDataNode* enumVectorNode = idh.FindNodeByPartialAddress({ AZ_CRC_CE("EnumVector") });
- ASSERT_NE(enumNode, nullptr);
- ASSERT_NE(enumVectorNode, nullptr);
- auto getEnumData = [&ec](const AzToolsFramework::InstanceDataNode& node) -> Uuid
- {
- Uuid id = Uuid::CreateNull();
- auto attribute = node.GetElementMetadata()->FindAttribute(AZ_CRC_CE("EnumType"));
- auto attributeData = azrtti_cast<AttributeData<AZ::TypeId>*>(attribute);
- if (attributeData)
- {
- id = attributeData->Get(&ec);
- }
- return id;
- };
- EXPECT_EQ(getEnumData(*enumNode), RttiTypeId<UnitTest::TestEnum>());
- const auto& vectorEntries = enumVectorNode->GetChildren();
- ASSERT_EQ(vectorEntries.size(), 1);
- EXPECT_EQ(getEnumData(*vectorEntries.begin()), RttiTypeId<UnitTest::TestEnum>());
- }
- };
- class GroupTestComponent : public AZ::Component
- {
- public:
- AZ_COMPONENT(GroupTestComponent, "{C088C81D-D59D-43F1-85F8-B2E591BABA36}")
- GroupTestComponent() = default;
- struct SubData
- {
- AZ_TYPE_INFO(SubData, "{983316B5-17C0-476E-9CEB-CA749B3ABE5D}");
- AZ_CLASS_ALLOCATOR(SubData, AZ::SystemAllocator);
- SubData() {}
- explicit SubData(int v) : m_int(v) {}
- explicit SubData(bool b) : m_bool(b) {}
- explicit SubData(float f) : m_float(f) {}
- ~SubData() = default;
- float m_float = 0.f;
- int m_int = 0;
- bool m_bool = true;
- };
- static void Reflect(AZ::ReflectContext* context)
- {
- if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serializeContext->Class<SubData>()
- ->Version(1)
- ->Field("SubInt", &SubData::m_int)
- ->Field("SubToggle", &SubData::m_bool)
- ->Field("SubFloat", &SubData::m_float)
- ;
- serializeContext->Class<GroupTestComponent, AZ::Component>()
- ->Version(1)
- ->Field("Float", &GroupTestComponent::m_float)
- ->Field("GroupToggle", &GroupTestComponent::m_groupToggle)
- ->Field("GroupFloat", &GroupTestComponent::m_groupFloat)
- ->Field("ToggleGroupInt", &GroupTestComponent::m_toggleGroupInt)
- ->Field("SubDataNormal", &GroupTestComponent::m_subGroupForNormal)
- ->Field("SubDataToggle", &GroupTestComponent::m_subGroupForToggle)
- ;
- if (AZ::EditContext* edit = serializeContext->GetEditContext())
- {
- edit->Class<GroupTestComponent>("Group Test Component", "Testing normal groups and toggle groups")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->DataElement(nullptr, &GroupTestComponent::m_float, "Float Field", "A float field")
- ->ClassElement(AZ::Edit::ClassElements::Group, "Normal Group")
- ->DataElement(nullptr, &GroupTestComponent::m_groupFloat, "Float Field", "A float field")
- ->DataElement(nullptr, &GroupTestComponent::m_subGroupForNormal, "Struct Field", "A sub data type")
- ->GroupElementToggle("Group Toggle", &GroupTestComponent::m_groupToggle)
- ->DataElement(nullptr, &GroupTestComponent::m_toggleGroupInt, "Normal Integer", "An Integer")
- ->DataElement(nullptr, &GroupTestComponent::m_subGroupForToggle, "Struct Field", "A sub data type")
- ;
- edit->Class<SubData>("SubGroup Test Component", "Testing nested normal groups and toggle groups")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->ClassElement(AZ::Edit::ClassElements::Group, "Normal SubGroup")
- ->DataElement(nullptr, &SubData::m_int, "SubGroup Int Field", "An int")
- ->GroupElementToggle("SubGroup Toggle", &SubData::m_bool)
- ->DataElement(nullptr, &SubData::m_float, "SubGroup Float Field", "An int")
- ;
- }
- }
- }
- void Activate() override
- {
- }
- void Deactivate() override
- {
- }
- float m_float = 0.f;
- float m_groupFloat = 0.f;
- int m_toggleGroupInt = 0;
- AZStd::string m_string;
- bool m_groupToggle = false;
- SubData m_subGroupForNormal;
- SubData m_subGroupForToggle;
- };
- class InstanceDataHierarchyGroupTestFixture : public LeakDetectionFixture
- {
- public:
- InstanceDataHierarchyGroupTestFixture() = default;
- AZStd::unique_ptr<SerializeContext> m_serializeContext;
- AZStd::unique_ptr<AZ::Entity> testEntity1;
- AzToolsFramework::InstanceDataHierarchy* instanceDataHierarchy;
- AzToolsFramework::InstanceDataNode* componentNode1 = nullptr;
- void SetUp() override
- {
- LeakDetectionFixture::SetUp();
- using AzToolsFramework::InstanceDataHierarchy;
- using AzToolsFramework::InstanceDataNode;
- m_serializeContext.reset(aznew AZ::SerializeContext());
- m_serializeContext.get()->CreateEditContext();
- Entity::Reflect(m_serializeContext.get());
- GroupTestComponent::Reflect(m_serializeContext.get());
- testEntity1.reset(new AZ::Entity());
- testEntity1->CreateComponent<GroupTestComponent>();
- instanceDataHierarchy = aznew InstanceDataHierarchy();
- instanceDataHierarchy->AddRootInstance(testEntity1.get());
- instanceDataHierarchy->Build(m_serializeContext.get(), 0);
- // Adding the nodes to a node stack
- auto rootNode = instanceDataHierarchy->GetRootNode();
- AZStd::stack<InstanceDataNode*> nodeStack;
- nodeStack.push(rootNode);
- while (!nodeStack.empty())
- {
- InstanceDataNode* node = nodeStack.top();
- nodeStack.pop();
- if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<GroupTestComponent>::Uuid())
- {
- componentNode1 = node;
- break;
- }
- for (InstanceDataNode& child : node->GetChildren())
- {
- nodeStack.push(&child);
- }
- }
- }
- void TearDown() override
- {
- m_serializeContext.reset();
- testEntity1.reset();
- delete instanceDataHierarchy;
- LeakDetectionFixture::TearDown();
- }
- };
- class InstanceDataHierarchyKeyedContainerTest
- : public LeakDetectionFixture
- {
- public:
- class CustomKeyWithoutStringRepresentation
- {
- public:
- AZ_TYPE_INFO(CustomKeyWithoutStringRepresentation, "{54E838DE-1A8D-4BBA-BD3A-D41886C439A9}");
- AZ_CLASS_ALLOCATOR(CustomKeyWithoutStringRepresentation, AZ::SystemAllocator);
- int m_value = 0;
- int operator<(const CustomKeyWithoutStringRepresentation& other) const
- {
- return m_value < other.m_value;
- }
- };
- class CustomKeyWithStringRepresentation
- {
- public:
- AZ_TYPE_INFO(CustomKeyWithStringRepresentation, "{51F7FB74-2991-4CC9-850A-8D5AA0732282}");
- AZ_CLASS_ALLOCATOR(CustomKeyWithStringRepresentation, AZ::SystemAllocator);
- static const char* KeyPrefix() { return "CustomKey"; }
- int m_value = 0;
- int operator<(const CustomKeyWithStringRepresentation& other) const
- {
- return m_value < other.m_value;
- }
- AZStd::string ToString() const
- {
- return AZStd::string::format("%s %i", KeyPrefix(), m_value);
- }
- };
- class KeyedContainer
- {
- public:
- AZ_TYPE_INFO(KeyedContainer, "{53A7416F-2D84-4256-97B0-BE4B6EF6DBAF}");
- AZ_CLASS_ALLOCATOR(KeyedContainer, AZ::SystemAllocator);
- AZStd::map<AZStd::string, float> m_map;
- AZStd::unordered_map<AZStd::pair<int, double>, int> m_unorderedMap;
- AZStd::set<int> m_set;
- AZStd::unordered_set<AZ::u64> m_unorderedSet;
- AZStd::unordered_multimap<int, AZStd::string> m_multiMap;
- AZStd::unordered_map<int, AZStd::unordered_map<int, int>> m_nestedMap;
- AZStd::map<CustomKeyWithoutStringRepresentation, int> m_uncollapsableMap;
- AZStd::map<CustomKeyWithStringRepresentation, int> m_collapsableMap;
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<CustomKeyWithoutStringRepresentation>()
- ->Field("value", &CustomKeyWithoutStringRepresentation::m_value);
- context.Class<CustomKeyWithStringRepresentation>()
- ->Field("value", &CustomKeyWithStringRepresentation::m_value);
- context.Class<KeyedContainer>()
- ->Field("map", &KeyedContainer::m_map)
- ->Field("unorderedMap", &KeyedContainer::m_unorderedMap)
- ->Field("set", &KeyedContainer::m_set)
- ->Field("unorderedSet", &KeyedContainer::m_unorderedSet)
- ->Field("multiMap", &KeyedContainer::m_multiMap)
- ->Field("nestedMap", &KeyedContainer::m_nestedMap)
- ->Field("uncollapsableMap", &KeyedContainer::m_uncollapsableMap)
- ->Field("collapsableMap", &KeyedContainer::m_collapsableMap);
- if (auto editContext = context.GetEditContext())
- {
- editContext->Class<CustomKeyWithStringRepresentation>("CustomKeyWithStringRepresentation", "")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->Attribute(AZ::Edit::Attributes::ConciseEditorStringRepresentation, &CustomKeyWithStringRepresentation::ToString);
- }
- }
- };
- struct KeyTestData
- {
- virtual void InsertAndVerifyKeys(AZ::SerializeContext::IDataContainer* container, void* key, void* instance, const AZ::SerializeContext::ClassElement* classElement) const = 0;
- virtual AZ::Uuid ExpectedKeyType() const = 0;
- virtual size_t NumberOfKeys() const = 0;
- virtual ~KeyTestData() {}
- };
- template <class T>
- struct TypedKeyTestData : public KeyTestData
- {
- AZStd::vector<T> keysToInsert;
- TypedKeyTestData(std::initializer_list<T> keys)
- : keysToInsert(keys)
- {
- }
- void InsertAndVerifyKeys(AZ::SerializeContext::IDataContainer* container, void* key, void* instance, const AZ::SerializeContext::ClassElement* classElement) const override
- {
- T* keyContainer = reinterpret_cast<T*>(key);
- for (const T& keyToInsert : keysToInsert)
- {
- *keyContainer = keyToInsert;
- void* element = container->ReserveElement(instance, classElement);
- auto associativeInterface = container->GetAssociativeContainerInterface();
- associativeInterface->SetElementKey(element, key);
- container->StoreElement(instance, element);
- auto lookupKey = associativeInterface->GetElementByKey(instance, classElement, (void*)(&keyToInsert));
- EXPECT_NE(lookupKey, nullptr);
- }
- }
- AZ::Uuid ExpectedKeyType() const override
- {
- return azrtti_typeid<AZ::Internal::RValueToLValueWrapper<T>>();
- }
- size_t NumberOfKeys() const override
- {
- return keysToInsert.size();
- }
- static AZStd::unique_ptr<TypedKeyTestData<T>> Create(std::initializer_list<T> keys)
- {
- return AZStd::make_unique<TypedKeyTestData<T>>(keys);
- }
- };
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- KeyedContainer::Reflect(serializeContext);
- KeyedContainer kc;
- InstanceDataHierarchy idh;
- idh.AddRootInstance(&kc, azrtti_typeid<KeyedContainer>());
- idh.Build(&serializeContext, 0);
- AZStd::unordered_map<AZ::u32, AZStd::unique_ptr<KeyTestData>> keyTestData;
- keyTestData[AZ_CRC_CE("map")] = TypedKeyTestData<AZStd::string>::Create({"A", "B", "lorem ipsum"});
- keyTestData[AZ_CRC_CE("unorderedMap")] = TypedKeyTestData<AZStd::pair<int, double>>::Create({ {5, 1.0}, {5, -2.0} });
- keyTestData[AZ_CRC_CE("set")] = TypedKeyTestData<int>::Create({2, 4, -255, 999});
- keyTestData[AZ_CRC_CE("unorderedSet")] = TypedKeyTestData<AZ::u64>::Create({500000, 9, 0, 42, 42});
- keyTestData[AZ_CRC_CE("multiMap")] = TypedKeyTestData<int>::Create({-1, 2, -3, 4, -5, 6});
- keyTestData[AZ_CRC_CE("nestedMap")] = TypedKeyTestData<int>::Create({1, 10, 100, 1000});
- keyTestData[AZ_CRC_CE("uncollapsableMap")] = TypedKeyTestData<CustomKeyWithoutStringRepresentation>::Create({{0}, {1}});
- keyTestData[AZ_CRC_CE("collapsableMap")] = TypedKeyTestData<CustomKeyWithStringRepresentation>::Create({{0}, {1}});
- auto insertKeysIntoContainer = [&serializeContext](AzToolsFramework::InstanceDataNode& node, KeyTestData* keysToInsert)
- {
- const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
- AZ::SerializeContext::IDataContainer* container = node.GetClassMetadata()->m_container;
- ASSERT_NE(element, nullptr);
- ASSERT_NE(container, nullptr);
- const AZ::SerializeContext::ClassElement* containerClassElement = container->GetElement(container->GetDefaultElementNameCrc());
- auto associativeInterface = container->GetAssociativeContainerInterface();
- ASSERT_NE(associativeInterface, nullptr);
- auto key = associativeInterface->CreateKey();
- auto attribute = containerClassElement ->FindAttribute(AZ_CRC_CE("KeyType"));
- auto attributeData = azrtti_cast<AttributeData<AZ::TypeId>*>(attribute);
- ASSERT_NE(attributeData, nullptr);
- auto keyId = attributeData->Get(node.FirstInstance());
- ASSERT_EQ(keyId, keysToInsert->ExpectedKeyType());
- // Ensure we can build an InstanceDataHierarchy at runtime from the container's KeyType
- InstanceDataHierarchy idh2;
- idh2.AddRootInstance(key.get(), keyId);
- idh2.Build(&serializeContext, 0);
- auto children = idh2.GetChildren();
- EXPECT_EQ(children.size(), 1);
- keysToInsert->InsertAndVerifyKeys(container, key.get(), node.FirstInstance(), element);
- };
- for (InstanceDataNode& node : idh.GetChildren())
- {
- const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
- auto insertIterator = keyTestData.find(element->m_nameCrc);
- ASSERT_NE(insertIterator, keyTestData.end());
- auto keysToInsert = insertIterator->second.get();
- insertKeysIntoContainer(node, keysToInsert);
- }
- auto nestedKeys = TypedKeyTestData<int>::Create({2, 4, 8, 16});
- idh.Build(&serializeContext, 0);
- for (InstanceDataNode& node : idh.GetChildren())
- {
- const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
- if (element->m_nameCrc == AZ_CRC_CE("nestedMap"))
- {
- auto children = node.GetChildren();
- // We should have entries for each inserted key in the nested map
- EXPECT_EQ(children.size(), keyTestData[AZ_CRC_CE("nestedMap")]->NumberOfKeys());
- for (AzToolsFramework::InstanceDataNode& child : children)
- {
- insertKeysIntoContainer(child.GetChildren().back(), nestedKeys.get());
- }
- }
- else if (element->m_nameCrc == AZ_CRC_CE("collapsableMap"))
- {
- auto children = node.GetChildren();
- EXPECT_GT(children.size(), 0);
- for (AzToolsFramework::InstanceDataNode& child : children)
- {
- // Ensure we're getting keys with the correct prefix based on the ConciseEditorStringRepresentation
- AZStd::string name = child.GetElementEditMetadata()->m_name;
- EXPECT_NE(name.find(CustomKeyWithStringRepresentation::KeyPrefix()), AZStd::string::npos);
- }
- }
- else if (element->m_nameCrc == AZ_CRC_CE("uncollapsableMap"))
- {
- auto children = node.GetChildren();
- EXPECT_GT(children.size(), 0);
- for (AzToolsFramework::InstanceDataNode& child : children)
- {
- auto keyValueChildren = child.GetChildren();
- EXPECT_EQ(keyValueChildren.size(), 2);
- auto keyValueChildrenIterator = keyValueChildren.begin();
- auto keyNode = *keyValueChildrenIterator;
- ++keyValueChildrenIterator;
- auto valueNode = *keyValueChildrenIterator;
- // Ensure key/value pairs that can't be collapsed get labels based on type
- EXPECT_EQ(AZ::Crc32(keyNode.GetElementEditMetadata()->m_name), AZ_CRC_CE("Key<CustomKeyWithoutStringRepresentation>"));
- EXPECT_EQ(AZ::Crc32(valueNode.GetElementEditMetadata()->m_name), AZ_CRC_CE("Value<int>"));
- }
- }
- }
- // Ensure IgnoreKeyValuePairs is respected
- idh.SetBuildFlags(InstanceDataHierarchy::Flags::IgnoreKeyValuePairs);
- idh.Build(&serializeContext, 0);
- for (InstanceDataNode& node : idh.GetChildren())
- {
- const AZ::SerializeContext::ClassElement* element = node.GetElementMetadata();
- if (element->m_nameCrc == AZ_CRC_CE("map") || element->m_nameCrc == AZ_CRC_CE("unorderedMap") || element->m_nameCrc == AZ_CRC_CE("nestedMap"))
- {
- for (InstanceDataNode& pair : node.GetChildren())
- {
- EXPECT_EQ(pair.GetChildren().size(), 2);
- }
- }
- }
- }
- };
- class InstanceDataHierarchyCompareAssociativeContainerTest
- : public LeakDetectionFixture
- {
- public:
- class Container
- {
- public:
- AZ_TYPE_INFO(Container, "{9920B5BD-F21C-4353-9449-9C3FD38E50FC}");
- AZ_CLASS_ALLOCATOR(Container, AZ::SystemAllocator);
- AZStd::unordered_map<AZStd::string, int> m_map;
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<Container>()
- ->Field("map", &Container::m_map);
- }
- };
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- Container::Reflect(serializeContext);
- Container c1;
- c1.m_map = {
- {"A", 1},
- {"B", 2},
- {"C", 3}
- };
- Container c2;
- c2.m_map = {
- {"C", 1},
- {"A", 2},
- {"B", 3}
- };
- Container c3;
- c3.m_map = {
- {"A", 2},
- {"D", 3}
- };
- auto testComparison = [&](Container& baseInstance,
- Container& compareInstance,
- AZStd::unordered_set<AZStd::string> expectedAdds,
- AZStd::unordered_set<AZStd::string> expectedRemoves,
- AZStd::unordered_set<AZStd::string> expectedChanges)
- {
- InstanceDataHierarchy idhBase;
- idhBase.AddRootInstance(&baseInstance, azrtti_typeid<Container>());
- idhBase.Build(&serializeContext, 0);
- InstanceDataHierarchy idhCompare;
- idhCompare.AddRootInstance(&compareInstance, azrtti_typeid<Container>());
- idhCompare.Build(&serializeContext, 0);
- AZStd::unordered_set<AZStd::string> actualAdds;
- AZStd::unordered_set<AZStd::string> actualRemoves;
- AZStd::unordered_set<AZStd::string> actualChanges;
- auto newNodeCB = [&](InstanceDataNode* newNode, AZStd::vector<AZ::u8>&)
- {
- actualAdds.insert(newNode->GetElementEditMetadata()->m_name);
- };
- auto removedNodeCB = [&](const InstanceDataNode* sourceNode, InstanceDataNode*)
- {
- actualRemoves.insert(sourceNode->GetElementEditMetadata()->m_name);
- };
- auto changedNodeCB = [&](const InstanceDataNode* sourceNode, const InstanceDataNode*, AZStd::vector<AZ::u8>&, AZStd::vector<AZ::u8>&)
- {
- actualChanges.insert(sourceNode->GetParent()->GetElementEditMetadata()->m_name);
- };
- InstanceDataHierarchy::CompareHierarchies(&idhBase,
- &idhCompare,
- &InstanceDataHierarchy::DefaultValueComparisonFunction,
- &serializeContext,
- newNodeCB,
- removedNodeCB,
- changedNodeCB
- );
- EXPECT_EQ(expectedAdds, actualAdds);
- EXPECT_EQ(expectedRemoves, actualRemoves);
- EXPECT_EQ(expectedChanges, actualChanges);
- };
- Container cCopy = c1;
- testComparison(c1, cCopy, {}, {}, {});
- testComparison(c1, c3, {"D", "[0]", "[1]"}, {"B", "C"}, {"A"});
- testComparison(c3, c1, {"B", "C", "[0]", "[1]"}, {"D"}, {"A"});
- testComparison(c1, c2, {}, {}, {"A", "B", "C"});
- }
- };
- class InstanceDataHierarchyElementTest
- : public LeakDetectionFixture
- {
- public:
- class UIElementContainer
- : public AZ::Component
- {
- public:
- AZ_COMPONENT(UIElementContainer, "{83B7BDFD-8B60-4C52-B7C5-BF3C824620F5}", AZ::Component);
- int m_data;
- static void Reflect(AZ::ReflectContext* context)
- {
- if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serialize->Class<UIElementContainer, AZ::Component>()
- ->Field("data", &UIElementContainer::m_data)
- ;
- if (auto editContext = serialize->GetEditContext())
- {
- editContext->Class<UIElementContainer>("Test", "")
- ->UIElement("TestHandler", "UIElement")
- ->DataElement(nullptr, &UIElementContainer::m_data)
- ->UIElement(AZ_CRC_CE("TestHandler2"), "UIElement2")
- ;
- }
- }
- }
- // AZ::Component overrides ...
- void Activate() override {}
- void Deactivate() override {}
- };
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- AZ::Entity::Reflect(&serializeContext);
- UIElementContainer::Reflect(&serializeContext);
- UIElementContainer test;
- InstanceDataHierarchy idh;
- idh.AddRootInstance(&test, azrtti_typeid<UIElementContainer>());
- idh.Build(&serializeContext, 0);
- auto children = idh.GetChildren();
- ASSERT_EQ(children.size(), 4);
- auto it = children.begin();
- // The first child will be the AZ::Component Id data that isn't
- // exposed to the EditContext, so it has no edit metadata
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "BaseClass1");
- EXPECT_EQ(it->GetElementEditMetadata(), nullptr);
- ++it;
- Crc32 uiHandler;
- EXPECT_EQ(it->ReadAttribute(AZ::Edit::UIHandlers::Handler, uiHandler), true);
- EXPECT_EQ(uiHandler, AZ_CRC_CE("TestHandler"));
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "UIElement");
- EXPECT_EQ(it->GetElementMetadata()->m_nameCrc, AZ_CRC_CE("UIElement"));
- // The "data" element is in between the two UIElements
- ++it;
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "data");
- ++it;
- uiHandler = Crc32();
- EXPECT_EQ(it->ReadAttribute(AZ::Edit::UIHandlers::Handler, uiHandler), true);
- EXPECT_EQ(uiHandler, AZ_CRC_CE("TestHandler2"));
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "UIElement2");
- EXPECT_EQ(it->GetElementMetadata()->m_nameCrc, AZ_CRC_CE("UIElement2"));
- }
- };
- class InstanceDataHierarchyEndGroupTest
- : public LeakDetectionFixture
- {
- public:
- class EndGroupContainer
- : public AZ::Component
- {
- public:
- AZ_COMPONENT(EndGroupContainer, "{0CFC7838-9B01-4E25-8932-1C1EE4FCEC77}", AZ::Component);
- int m_rootData;
- bool m_otherRootData;
- int m_group1Data;
- int m_group2Data;
- int m_finalRootData;
- static void Reflect(AZ::ReflectContext* context)
- {
- if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serialize->Class<EndGroupContainer, AZ::Component>()
- ->Field("rootData", &EndGroupContainer::m_rootData)
- ->Field("otherRootData", &EndGroupContainer::m_otherRootData)
- ->Field("group1Data", &EndGroupContainer::m_group1Data)
- ->Field("group2Data", &EndGroupContainer::m_group2Data)
- ->Field("finalRootData", &EndGroupContainer::m_finalRootData)
- ;
- if (auto editContext = serialize->GetEditContext())
- {
- editContext->Class<EndGroupContainer>("EndGroupTest", "")
- ->DataElement(nullptr, &EndGroupContainer::m_rootData)
- ->DataElement(nullptr, &EndGroupContainer::m_otherRootData)
- ->ClassElement(AZ::Edit::ClassElements::Group, "First Group")
- ->DataElement(nullptr, &EndGroupContainer::m_group1Data)
- ->ClassElement(AZ::Edit::ClassElements::Group, "Second Group")
- ->DataElement(nullptr, &EndGroupContainer::m_group2Data)
- ->EndGroup()
- ->DataElement(nullptr, &EndGroupContainer::m_finalRootData)
- ;
- }
- }
- }
- // AZ::Component overrides ...
- void Activate() override {}
- void Deactivate() override {}
- };
- void run()
- {
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- AZ::Entity::Reflect(&serializeContext);
- EndGroupContainer::Reflect(&serializeContext);
- EndGroupContainer test;
- AzToolsFramework::InstanceDataHierarchy idh;
- idh.AddRootInstance(&test, azrtti_typeid<EndGroupContainer>());
- idh.Build(&serializeContext, 0);
- auto children = idh.GetChildren();
- ASSERT_EQ(children.size(), 6);
- auto it = children.begin();
- // The first child will be the AZ::Component Id data,
- // which we don't care about for this test
- ++it;
- // The next two children are root data, which both should
- // have no group element
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "rootData");
- EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
- ++it;
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "otherRootData");
- EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
- // The next data should be in the first group
- ++it;
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "group1Data");
- EXPECT_STREQ(it->GetGroupElementMetadata()->m_description, "First Group");
- // The following data should be in the second group
- ++it;
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "group2Data");
- EXPECT_STREQ(it->GetGroupElementMetadata()->m_description, "Second Group");
- // The final data should have no group since we ended the second group
- ++it;
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "finalRootData");
- EXPECT_EQ(it->GetGroupElementMetadata(), nullptr);
- }
- };
- class InstanceDataHierarchyAggregateInstanceTest
- : public LeakDetectionFixture
- {
- public:
- class AggregatedContainer
- {
- public:
- AZ_TYPE_INFO(AggregatedContainer, "{42E09F38-2D26-4FED-9901-06003A030ED5}");
- AZ_CLASS_ALLOCATOR(AggregatedContainer, AZ::SystemAllocator);
- int m_aggregated;
- int m_notAggregated;
- static void Reflect(AZ::SerializeContext& context)
- {
- context.Class<AggregatedContainer>()
- ->Field("aggregatedDataElement", &AggregatedContainer::m_aggregated)
- ->Field("notAggregatedDataElement", &AggregatedContainer::m_notAggregated)
- ;
- if (auto editContext = context.GetEditContext())
- {
- // By default, DataElements accept multi-edit and UIElements do not
- editContext->Class<AggregatedContainer>("Test", "")
- ->DataElement(nullptr, &AggregatedContainer::m_aggregated)
- ->DataElement(nullptr, &AggregatedContainer::m_notAggregated)
- ->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, false)
- ->UIElement("TestHandler", "aggregatedUIElement")
- ->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, true)
- ->UIElement(AZ_CRC_CE("TestHandler2"), "notAggregatedUIElement")
- ;
- }
- }
- };
- void run()
- {
- using namespace AzToolsFramework;
- AZ::SerializeContext serializeContext;
- serializeContext.CreateEditContext();
- AggregatedContainer::Reflect(serializeContext);
- InstanceDataHierarchy idh;
- AZStd::list<AggregatedContainer> containers;
- for (int i = 0; i < 5; ++i)
- {
- AggregatedContainer& container = containers.emplace_back();
- idh.AddRootInstance(&container, azrtti_typeid<AggregatedContainer>());
- idh.Build(&serializeContext, 0);
- auto children = idh.GetChildren();
- // If we have multiple instances, the two non-aggregating elements should go away
- ASSERT_EQ(children.size(), i == 0 ? 4 : 2);
- auto it = children.begin();
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "aggregatedDataElement");
- ++it;
- if (i == 0)
- {
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "notAggregatedDataElement");
- ++it;
- }
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "aggregatedUIElement");
- ++it;
- if (i == 0)
- {
- EXPECT_STREQ(it->GetElementMetadata()->m_name, "notAggregatedUIElement");
- ++it;
- }
- }
- }
- };
- TEST_F(InstanceDataHierarchyBasicTest, Test)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyCopyContainerChangesTest, Test)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyEnumContainerTest, Test)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyKeyedContainerTest, Test)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyKeyedContainerTest, RemovingMultipleItemsFromContainerDoesNotCrash)
- {
- using TestMap = AZStd::unordered_map<double, double>;
- TestMap testMap;
- 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} };
- AZ::GenericClassInfo* mapGenericClassInfo = AZ::SerializeGenericTypeInfo<TestMap>::GetGenericInfo();
- AZ::SerializeContext::ClassData* mapClassData = mapGenericClassInfo->GetClassData();
- ASSERT_NE(nullptr, mapClassData);
- AZ::SerializeContext::IDataContainer* mapDataContainer = mapClassData->m_container;
- ASSERT_NE(nullptr, mapDataContainer);
- auto associativeInterface = mapDataContainer->GetAssociativeContainerInterface();
- AZ::SerializeContext::ClassElement classElement;
- AZ::SerializeContext::DataElement dataElement;
- dataElement.m_nameCrc = mapDataContainer->GetDefaultElementNameCrc();
- EXPECT_TRUE(mapDataContainer->GetElement(classElement, dataElement));
- AZStd::vector<double> keyRemovalContainer;
- keyRemovalContainer.reserve(valuesToInsert.size());
- for (const AZStd::pair<double, double>& valueToInsert : valuesToInsert)
- {
- void* newElement = mapDataContainer->ReserveElement(&testMap, &classElement);
- *reinterpret_cast<typename TestMap::value_type*>(newElement) = valueToInsert;
- mapDataContainer->StoreElement(&testMap, newElement);
- keyRemovalContainer.push_back(valueToInsert.first);
- }
- EXPECT_EQ(valuesToInsert.size(), testMap.size());
- for (const AZStd::pair<double, double>& testValue : valuesToInsert)
- {
- // Make sure all elements within initializer_list is in the map
- void* lookupValue = associativeInterface->GetElementByKey(&testMap, &classElement, &testValue.first);
- EXPECT_NE(nullptr, lookupValue);
- }
-
- // Shuffle the keys around and attempt to remove the keys using IDataContainer::RemoveElement
- SerializeContext serializeContext;
- const uint32_t rngSeed = std::random_device{}();
- std::mt19937 mtTwisterRng(rngSeed);
- std::shuffle(keyRemovalContainer.begin(), keyRemovalContainer.end(), mtTwisterRng);
- for (double key : keyRemovalContainer)
- {
- void* valueToRemove = associativeInterface->GetElementByKey(&testMap, &classElement, &key);
- EXPECT_TRUE(mapDataContainer->RemoveElement(&testMap, valueToRemove, &serializeContext));
- }
- EXPECT_EQ(0, mapDataContainer->Size(&testMap));
- }
- TEST_F(InstanceDataHierarchyCompareAssociativeContainerTest, TestComparingAssociativeContainers)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyElementTest, TestLayingOutUIAndDataElements)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyEndGroupTest, TestEndGroup)
- {
- run();
- }
- TEST_F(InstanceDataHierarchyAggregateInstanceTest, TestRespectingAggregateInstanceVisibility)
- {
- run();
- }
- // Test to validate that the only ClassElement::Group nodes are ToggleGroups
- TEST_F(InstanceDataHierarchyGroupTestFixture, GroupToggleIsClassElementGroup)
- {
- using AzToolsFramework::InstanceDataHierarchy;
- using AzToolsFramework::InstanceDataNode;
- for (auto child : componentNode1->GetChildren())
- {
- AZStd::string childName(child.GetElementMetadata()->m_name);
- if (childName.compare("GroupToggle") == 0)
- {
- EXPECT_EQ(child.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
- }
- if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
- {
- for (auto subChild : child.GetChildren())
- {
- childName = subChild.GetElementMetadata()->m_name;
- if (childName.compare("SubToggle") == 0)
- {
- EXPECT_EQ(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
- }
- else
- {
- EXPECT_NE(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
- }
- }
- }
- }
- }
- // Test to ensure that each node has been assigned under the proper group and the group hierarchy is structured correctly
- TEST_F(InstanceDataHierarchyGroupTestFixture, ValidatingGroupAndSubGroupHierarchy)
- {
- using AzToolsFramework::InstanceDataHierarchy;
- using AzToolsFramework::InstanceDataNode;
- for (auto child : componentNode1->GetChildren())
- {
- AZStd::string childName(child.GetElementMetadata()->m_name);
- if (childName.compare("GroupFloat") == 0)
- {
- EXPECT_STREQ(child.GetGroupElementMetadata()->m_description, "Normal Group");
- }
- if (childName.compare("ToggleGroupInt") == 0)
- {
- EXPECT_STREQ(child.GetGroupElementMetadata()->m_description, "Group Toggle");
- }
- if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
- {
- for (auto subChild : child.GetChildren())
- {
- childName = subChild.GetElementMetadata()->m_name;
- if (childName.compare("SubInt") == 0)
- {
- EXPECT_STREQ(subChild.GetGroupElementMetadata()->m_description, "Normal SubGroup");
- }
- if (childName.compare("SubFloat") == 0)
- {
- EXPECT_STREQ(subChild.GetGroupElementMetadata()->m_description, "SubGroup Toggle");
- }
- }
- }
- }
- }
- class InstanceDataHierarchyGroupTestFixtureParameterized
- : public InstanceDataHierarchyGroupTestFixture
- , public ::testing::WithParamInterface<const char*>
- {
- };
- INSTANTIATE_TEST_SUITE_P(
- InstanceDataHierarchyGroupTestFixture,
- InstanceDataHierarchyGroupTestFixtureParameterized,
- ::testing::Values("GroupFloat", "GroupToggle", "ToggleGroupInt", "SubInt", "SubToggle", "SubFloat"));
- // Test to validate that each node in a group and Subgroup has the correct parent
- TEST_P(InstanceDataHierarchyGroupTestFixtureParameterized, ValidatingGroupAndSubGroupParents)
- {
- using AzToolsFramework::InstanceDataHierarchy;
- using AzToolsFramework::InstanceDataNode;
- const char* paramName = GetParam();
- for (auto child : componentNode1->GetChildren())
- {
- AZStd::string childName(child.GetElementMetadata()->m_name);
- if (childName.compare(paramName) == 0)
- {
- EXPECT_STREQ(child.GetParent()->GetClassMetadata()->m_name, "GroupTestComponent");
- }
- if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
- {
- for (auto subChild : child.GetChildren())
- {
- childName = subChild.GetElementMetadata()->m_name;
- if (childName.compare(paramName) == 0)
- {
- EXPECT_STREQ(subChild.GetParent()->GetClassMetadata()->m_name, "SubData");
- }
- }
- }
- }
- }
- } // namespace UnitTest
|