You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzCore/Tests/Components.cpp

2031 lines
83 KiB
C++

/*
* 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 "FileIOBaseTestTypes.h"
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Sfmt.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Debug/FrameProfilerBus.h>
#include <AzCore/Debug/FrameProfilerComponent.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/parallel/containers/concurrent_unordered_set.h>
#include <AZTestShared/Utils/Utils.h>
#include <AzTest/Utils.h>
#if defined(HAVE_BENCHMARK)
#include <benchmark/benchmark.h>
#endif
using namespace AZ;
using namespace AZ::Debug;
// This test needs to be outside of a fixture, as it needs to bring up its own allocators
TEST(ComponentApplication, Test)
{
ComponentApplication app;
//////////////////////////////////////////////////////////////////////////
// Create application environment code driven
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024;
appDesc.m_recordingMode = AllocationRecords::RECORD_FULL;
appDesc.m_stackRecordLevels = 20;
Entity* systemEntity = app.Create(appDesc);
systemEntity->CreateComponent<MemoryComponent>();
systemEntity->CreateComponent<StreamerComponent>();
systemEntity->CreateComponent("{CAE3A025-FAC9-4537-B39E-0A800A2326DF}"); // JobManager component
systemEntity->CreateComponent("{D5A73BCC-0098-4d1e-8FE4-C86101E374AC}"); // AssetDatabase component
systemEntity->Init();
systemEntity->Activate();
app.Destroy();
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Create application environment data driven
systemEntity = app.Create(appDesc);
systemEntity->Init();
systemEntity->Activate();
app.Destroy();
//////////////////////////////////////////////////////////////////////////
}
namespace UnitTest
{
class Components
: public AllocatorsFixture
{
public:
Components()
: AllocatorsFixture()
{
}
};
//////////////////////////////////////////////////////////////////////////
// Some component message bus, this is not really part of the component framework
// but this is way components are suppose to communicate... using the EBus
class SimpleComponentMessages
: public AZ::EBusTraits
{
public:
virtual ~SimpleComponentMessages() {}
virtual void DoA(int a) = 0;
virtual void DoB(int b) = 0;
};
typedef AZ::EBus<SimpleComponentMessages> SimpleComponentMessagesBus;
//////////////////////////////////////////////////////////////////////////
class SimpleComponent
: public Component
, public SimpleComponentMessagesBus::Handler
, public TickBus::Handler
{
public:
AZ_RTTI(SimpleComponent, "{6DFA17AF-014C-4624-B453-96E1F9807491}", Component)
AZ_CLASS_ALLOCATOR(SimpleComponent, SystemAllocator, 0)
SimpleComponent()
: m_a(0)
, m_b(0)
, m_isInit(false)
, m_isActivated(false)
{
}
//////////////////////////////////////////////////////////////////////////
// Component base
void Init() override { m_isInit = true; m_isTicked = false; }
void Activate() override
{
SimpleComponentMessagesBus::Handler::BusConnect();
// This is a very tricky (but valid example)... here we use the TickBus... thread safe
// event queue, to queue the connection to be executed from the main thread, just before tick.
// By using this even though TickBus is executed in single thread mode (main thread) for
// performance reasons, you can technically issue command from multiple thread.
// This requires advanced knowledge of the EBus and it's NOT recommended as a schema for
// generic functionality. You should just call TickBus::Handler::BusConnect(GetEntityId()); in place
// make sure you are doing this from the main thread.
EBUS_QUEUE_FUNCTION(TickBus, &TickBus::Handler::BusConnect, this);
m_isActivated = true;
}
void Deactivate() override
{
SimpleComponentMessagesBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
m_isActivated = false;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// SimpleComponentMessagesBus
void DoA(int a) override { m_a = a; }
void DoB(int b) override { m_b = b; }
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TickBus
void OnTick(float deltaTime, ScriptTimePoint time) override
{
m_isTicked = true;
AZ_TEST_ASSERT(deltaTime >= 0);
AZ_TEST_ASSERT(time.Get().time_since_epoch().count() != 0);
}
//////////////////////////////////////////////////////////////////////////
int m_a;
int m_b;
bool m_isInit;
bool m_isActivated;
bool m_isTicked;
};
// Example how to implement custom desciptors
class SimpleComponentDescriptor
: public ComponentDescriptorHelper<SimpleComponent>
{
public:
void Reflect(ReflectContext* /*reflection*/) const override
{
}
};
TEST_F(Components, SimpleTest)
{
SimpleComponentDescriptor descriptor;
ComponentApplication componentApp;
ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture)
ComponentApplication::StartupParameters startupParams;
startupParams.m_allocator = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
Entity* systemEntity = componentApp.Create(desc, startupParams);
AZ_TEST_ASSERT(systemEntity);
systemEntity->Init();
Entity* entity = aznew Entity("My Entity");
AZ_TEST_ASSERT(entity->GetState() == Entity::State::Constructed);
// Make sure its possible to set the id of the entity before inited.
AZ::EntityId newId = AZ::Entity::MakeId();
entity->SetId(newId);
AZ_TEST_ASSERT(entity->GetId() == newId);
AZ_TEST_START_TRACE_SUPPRESSION;
entity->SetId(SystemEntityId); // this is disallowed.
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
// we can always create components directly when we have the factory
// but it is intended to be used in generic way...
SimpleComponent* comp1 = aznew SimpleComponent;
AZ_TEST_ASSERT(comp1 != nullptr);
AZ_TEST_ASSERT(comp1->GetEntity() == nullptr);
AZ_TEST_ASSERT(comp1->GetId() == InvalidComponentId);
bool result = entity->AddComponent(comp1);
AZ_TEST_ASSERT(result);
// try to find it
SimpleComponent* comp2 = entity->FindComponent<SimpleComponent>();
AZ_TEST_ASSERT(comp1 == comp2);
// init entity
entity->Init();
AZ_TEST_ASSERT(entity->GetState() == Entity::State::Init);
AZ_TEST_ASSERT(comp1->m_isInit);
AZ_TEST_ASSERT(comp1->GetEntity() == entity);
AZ_TEST_ASSERT(comp1->GetId() != InvalidComponentId); // id is set only for attached components
// Make sure its NOT possible to set the id of the entity after INIT
newId = AZ::Entity::MakeId();
AZ::EntityId oldID = entity->GetId();
AZ_TEST_START_TRACE_SUPPRESSION;
entity->SetId(newId); // this should not work because its init.
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_ASSERT(entity->GetId() == oldID); // id should be unaffected.
// try to send a component message, since it's not active nobody should listen to it
EBUS_EVENT(SimpleComponentMessagesBus, DoA, 1);
AZ_TEST_ASSERT(comp1->m_a == 0); // it should still be 0
// activate
entity->Activate();
AZ_TEST_ASSERT(entity->GetState() == Entity::State::Active);
AZ_TEST_ASSERT(comp1->m_isActivated);
// now the component should be active responsive to message
EBUS_EVENT(SimpleComponentMessagesBus, DoA, 1);
AZ_TEST_ASSERT(comp1->m_a == 1);
// Make sure its NOT possible to set the id of the entity after Activate
newId = AZ::Entity::MakeId();
AZ_TEST_START_TRACE_SUPPRESSION;
entity->SetId(newId); // this should not work because its init.
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
// test the tick events
componentApp.Tick(); // first tick will set-up timers and have 0 delta time
AZ_TEST_ASSERT(comp1->m_isTicked);
componentApp.Tick(); // this will dispatch actual valid delta time
// make sure we can't remove components while active
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_TEST_ASSERT(entity->RemoveComponent(comp1) == false);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
// make sure we can't add components while active
{
SimpleComponent anotherComp;
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_TEST_ASSERT(entity->AddComponent(&anotherComp) == false);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_TEST_ASSERT(entity->CreateComponent<SimpleComponent>() == nullptr);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_TEST_ASSERT(entity->CreateComponent(azrtti_typeid<SimpleComponent>()) == nullptr);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
// deactivate
entity->Deactivate();
AZ_TEST_ASSERT(entity->GetState() == Entity::State::Init);
AZ_TEST_ASSERT(comp1->m_isActivated == false);
// try to send a component message, since it's not active nobody should listen to it
EBUS_EVENT(SimpleComponentMessagesBus, DoA, 2);
AZ_TEST_ASSERT(comp1->m_a == 1);
// make sure we can remove components
AZ_TEST_ASSERT(entity->RemoveComponent(comp1));
AZ_TEST_ASSERT(comp1->GetEntity() == nullptr);
AZ_TEST_ASSERT(comp1->GetId() == InvalidComponentId);
delete comp1;
delete entity;
descriptor.BusDisconnect(); // disconnect from the descriptor bus (so the app doesn't try to clean us up)
}
//////////////////////////////////////////////////////////////////////////
// Component A
class ComponentA
: public Component
{
public:
AZ_CLASS_ALLOCATOR(ComponentA, SystemAllocator, 0)
AZ_RTTI(ComponentA, "{4E93E03A-0B71-4630-ACCA-C6BB78E6DEB9}", Component)
void Activate() override {}
void Deactivate() override {}
};
/// Custom descriptor... example
class ComponentADescriptor
: public ComponentDescriptorHelper<ComponentA>
{
public:
AZ_CLASS_ALLOCATOR(ComponentADescriptor, SystemAllocator, 0);
ComponentADescriptor()
: m_isDependent(false)
{
}
void GetProvidedServices(DependencyArrayType& provided, const Component* instance) const override
{
(void)instance;
provided.push_back(AZ_CRC("ServiceA", 0x808b9021));
}
void GetDependentServices(DependencyArrayType& dependent, const Component* instance) const override
{
(void)instance;
if (m_isDependent)
{
dependent.push_back(AZ_CRC("ServiceD", 0xf0e164ae));
}
}
void Reflect(ReflectContext* /*reflection*/) const override {}
bool m_isDependent;
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component B
class ComponentB
: public Component
{
public:
AZ_COMPONENT(ComponentB, "{30B266B3-AFD6-4173-8BEB-39134A3167E3}")
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceB", 0x1982c19b)); }
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceE", 0x87e65438)); }
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("ServiceF", 0x1eef0582)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component C
class ComponentC
: public Component
{
public:
AZ_COMPONENT(ComponentC, "{A24C5D97-641F-4A92-90BB-647213A9D054}");
void Activate() override {}
void Deactivate() override {}
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC("ServiceB", 0x1982c19b)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component D
class ComponentD
: public Component
{
public:
AZ_COMPONENT(ComponentD, "{90888AD7-9D15-4356-8B95-C233A2E3083C}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceD", 0xf0e164ae)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component E
class ComponentE
: public Component
{
public:
AZ_COMPONENT(ComponentE, "{8D28A94A-9F70-4ADA-999E-D8A56A3048FB}", Component);
void Activate() override {}
void Deactivate() override {}
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceD", 0xf0e164ae)); dependent.push_back(AZ_CRC("ServiceA", 0x808b9021)); }
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceE", 0x87e65438)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component E2 - provides ServiceE but has no dependencies
class ComponentE2
: public Component
{
public:
AZ_COMPONENT(ComponentE2, "{33FE383C-92E0-48A4-A89A-91283DFC714A}", Component);
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceE", 0x87e65438)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component F
class ComponentF
: public Component
{
public:
AZ_COMPONENT(ComponentF, "{9A04F820-DFB6-42CF-9D1B-F970CEF1A02A}");
void Activate() override {}
void Deactivate() override {}
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("ServiceA", 0x808b9021)); }
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceF", 0x1eef0582)); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component G - has cyclic dependency with H
class ComponentG
: public Component
{
public:
AZ_COMPONENT(ComponentG, "{1CF8894A-CFE4-42FE-8127-63416DF734E1}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceG")); }
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC("ServiceH")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component H - has cyclic dependency with G
class ComponentH
: public Component
{
public:
AZ_COMPONENT(ComponentH, "{2FCF9245-B579-45D1-950B-A6779CA16F66}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceH")); }
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC("ServiceG")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component I - incompatible with other components providing the same service
class ComponentI
: public Component
{
public:
AZ_COMPONENT(ComponentI, "{5B509DB8-5D8A-4141-8701-4244E2F99025}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceI")); }
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceI")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component J - "accidentally" provides same service twice
class ComponentJ
: public Component
{
public:
AZ_COMPONENT(ComponentJ, "{67D56E5D-AB39-4BC3-AB1B-5B1F622E2A7F}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceJ")); provided.push_back(AZ_CRC("ServiceJ")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component K - depends on component that declared its provided service twice
class ComponentK
: public Component
{
public:
AZ_COMPONENT(ComponentK, "{9FEB506A-03BD-485B-A5D5-133B34E290F5}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceK")); }
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceJ")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component L - "accidentally" depends on same service twice
class ComponentL
: public Component
{
public:
AZ_COMPONENT(ComponentL, "{17A80803-C0F1-4595-A29D-AAD81D69B82E}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceL")); }
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceA")); dependent.push_back(AZ_CRC("ServiceA")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component M - "accidentally" depends on and requires the same service
class ComponentM
: public Component
{
public:
AZ_COMPONENT(ComponentM, "{74A118BC-2049-4C90-82B1-094934BD86F7}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceM")); }
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceA")); }
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("ServiceA")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component N - "accidentally" lists an incompatibility twice
class ComponentN
: public Component
{
public:
AZ_COMPONENT(ComponentN, "{B1026620-ED77-4897-B3EF-D03D4DDAF84B}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceN")); }
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceA")); provided.push_back(AZ_CRC("ServiceA")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component O - "accidentally" lists its own service twice in incompatibility list
class ComponentO
: public Component
{
public:
AZ_COMPONENT(ComponentO, "{14916FA3-8A74-4974-AED9-43CB222C6883}");
void Activate() override {}
void Deactivate() override {}
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceO")); }
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ServiceO")); provided.push_back(AZ_CRC("ServiceO")); }
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Component P - no services at all
class ComponentP
: public Component
{
public:
AZ_COMPONENT(ComponentP, "{0D71F310-FEBC-418D-9C4B-847C89DF6606}");
void Activate() override {}
void Deactivate() override {}
static void Reflect(ReflectContext* /*reflection*/) {}
};
//////////////////////////////////////////////////////////////////////////
class ComponentDependency
: public Components
{
protected:
void SetUp() override
{
AllocatorsFixture::SetUp();
// component descriptors are cleaned up when application shuts down
m_descriptorComponentA = aznew ComponentADescriptor;
aznew ComponentB::DescriptorType;
aznew ComponentC::DescriptorType;
aznew ComponentD::DescriptorType;
aznew ComponentE::DescriptorType;
aznew ComponentE2::DescriptorType;
aznew ComponentF::DescriptorType;
aznew ComponentG::DescriptorType;
aznew ComponentH::DescriptorType;
aznew ComponentI::DescriptorType;
aznew ComponentJ::DescriptorType;
aznew ComponentK::DescriptorType;
aznew ComponentL::DescriptorType;
aznew ComponentM::DescriptorType;
aznew ComponentN::DescriptorType;
aznew ComponentO::DescriptorType;
aznew ComponentP::DescriptorType;
m_componentApp = aznew ComponentApplication();
ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture in Components)
ComponentApplication::StartupParameters startupParams;
startupParams.m_allocator = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
Entity* systemEntity = m_componentApp->Create(desc, startupParams);
systemEntity->Init();
m_entity = aznew Entity();
}
void TearDown() override
{
delete m_entity;
delete m_componentApp;
AllocatorsFixture::TearDown();
}
void CreateComponents_ABCDE()
{
m_entity->CreateComponent<ComponentA>();
m_entity->CreateComponent<ComponentB>();
m_entity->CreateComponent<ComponentC>();
m_entity->CreateComponent<ComponentD>();
m_entity->CreateComponent<ComponentE>();
}
ComponentADescriptor* m_descriptorComponentA;
ComponentApplication* m_componentApp;
Entity *m_entity; // an entity to mess with in each test
};
TEST_F(ComponentDependency, FixtureSanityCheck)
{
// Tests that Setup/TearDown work as expected
}
TEST_F(ComponentDependency, IsComponentReadyToAdd_ExaminesRequiredServices)
{
ComponentC* componentC = aznew ComponentC;
ComponentDescriptor::DependencyArrayType requiredServices;
EXPECT_FALSE(m_entity->IsComponentReadyToAdd(componentC, &requiredServices)); // we require B component to be added
ASSERT_EQ(1, requiredServices.size());
Crc32 requiredService = requiredServices[0];
EXPECT_EQ(Crc32("ServiceB"), requiredService);
m_entity->CreateComponent<ComponentB>();
EXPECT_TRUE(m_entity->IsComponentReadyToAdd(componentC)); // we require B component to be added
delete componentC;
}
TEST_F(ComponentDependency, IsComponentReadyToAdd_ExaminesIncompatibleServices)
{
ComponentA* componentA = m_entity->CreateComponent<ComponentA>();
ComponentB* componentB = m_entity->CreateComponent<ComponentB>(); // B incompatible with F
ComponentF* componentF = aznew ComponentF(); // F incompatible with A
Entity::ComponentArrayType incompatible;
EXPECT_FALSE(m_entity->IsComponentReadyToAdd(componentF, nullptr, &incompatible));
EXPECT_EQ(2, incompatible.size());
bool incompatibleWithComponentA = AZStd::find(incompatible.begin(), incompatible.end(), componentA) != incompatible.end();
bool incompatibleWithComponentB = AZStd::find(incompatible.begin(), incompatible.end(), componentB) != incompatible.end();
EXPECT_TRUE(incompatibleWithComponentA);
EXPECT_TRUE(incompatibleWithComponentB);
delete componentF;
}
TEST_F(ComponentDependency, Init_DoesNotChangeComponentOrder)
{
Entity::ComponentArrayType originalOrder = m_entity->GetComponents();
m_entity->Init(); // Init should not change the component order
EXPECT_EQ(originalOrder, m_entity->GetComponents());
}
TEST_F(ComponentDependency, Activate_SortsComponentsCorrectly)
{
CreateComponents_ABCDE();
m_entity->Init();
m_entity->Activate(); // here components will be sorted based on order
const Entity::ComponentArrayType& components = m_entity->GetComponents();
EXPECT_TRUE(components[0]->RTTI_IsTypeOf(AzTypeInfo<ComponentA>::Uuid()));
EXPECT_TRUE(components[1]->RTTI_IsTypeOf(AzTypeInfo<ComponentD>::Uuid()));
EXPECT_TRUE(components[2]->RTTI_IsTypeOf(AzTypeInfo<ComponentE>::Uuid()));
EXPECT_TRUE(components[3]->RTTI_IsTypeOf(AzTypeInfo<ComponentB>::Uuid()));
EXPECT_TRUE(components[4]->RTTI_IsTypeOf(AzTypeInfo<ComponentC>::Uuid()));
}
TEST_F(ComponentDependency, Deactivate_DoesNotChangeComponentOrder)
{
CreateComponents_ABCDE();
m_entity->Init();
m_entity->Activate();
Entity::ComponentArrayType orderAfterActivate = m_entity->GetComponents();
m_entity->Deactivate();
EXPECT_EQ(orderAfterActivate, m_entity->GetComponents());
}
TEST_F(ComponentDependency, CachedDependency_PreventsComponentSort)
{
CreateComponents_ABCDE();
m_entity->Init();
m_entity->Activate();
m_entity->Deactivate();
Entity::ComponentArrayType originalSortedOrder = m_entity->GetComponents();
m_descriptorComponentA->m_isDependent = true; // now A should depend on D (but only after we notify the entity of the change)
m_entity->Activate();
// order should be unchanged (because we cache the dependency)
EXPECT_EQ(originalSortedOrder, m_entity->GetComponents());
}
TEST_F(ComponentDependency, InvalidatingDependency_CausesComponentSort)
{
CreateComponents_ABCDE();
m_entity->Init();
m_entity->Activate();
m_entity->Deactivate();
m_descriptorComponentA->m_isDependent = true; // now A should depend on D
m_entity->InvalidateDependencies();
m_entity->Activate();
// check the new order
const Entity::ComponentArrayType& components = m_entity->GetComponents();
EXPECT_TRUE(components[0]->RTTI_IsTypeOf(AzTypeInfo<ComponentD>::Uuid()));
EXPECT_TRUE(components[1]->RTTI_IsTypeOf(AzTypeInfo<ComponentA>::Uuid()));
EXPECT_TRUE(components[2]->RTTI_IsTypeOf(AzTypeInfo<ComponentE>::Uuid()));
EXPECT_TRUE(components[3]->RTTI_IsTypeOf(AzTypeInfo<ComponentB>::Uuid()));
EXPECT_TRUE(components[4]->RTTI_IsTypeOf(AzTypeInfo<ComponentC>::Uuid()));
}
TEST_F(ComponentDependency, IsComponentReadyToRemove_ExaminesRequiredServices)
{
ComponentB* componentB = m_entity->CreateComponent<ComponentB>();
ComponentC* componentC = m_entity->CreateComponent<ComponentC>();
Entity::ComponentArrayType requiredComponents;
EXPECT_FALSE(m_entity->IsComponentReadyToRemove(componentB, &requiredComponents)); // component C requires us
ASSERT_EQ(1, requiredComponents.size());
Component* requiredComponent = requiredComponents[0];
EXPECT_EQ(componentC, requiredComponent);
m_entity->RemoveComponent(componentC);
delete componentC;
EXPECT_TRUE(m_entity->IsComponentReadyToRemove(componentB)); // we should be ready for remove
}
// there was once a bug where, if multiple different component types provided the same service,
// those components didn't necessarily sort before components that depended on that service
TEST_F(ComponentDependency, DependingOnSameServiceFromTwoDifferentComponents_PutsServiceProvidersFirst)
{
m_entity->CreateComponent<ComponentD>(); // no dependencies
Component* e2 = m_entity->CreateComponent<ComponentE2>(); // no dependencies
Component* e = m_entity->CreateComponent<ComponentE>(); // depends on ServiceD
Component* b = m_entity->CreateComponent<ComponentB>(); // depends on ServiceE (provided by E and E2)
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
const AZStd::vector<Component*>& components = m_entity->GetComponents();
auto locationB = AZStd::find(components.begin(), components.end(), b);
auto locationE = AZStd::find(components.begin(), components.end(), e);
auto locationE2 = AZStd::find(components.begin(), components.end(), e2);
EXPECT_LT(locationE, locationB);
EXPECT_LT(locationE2, locationB);
}
TEST_F(ComponentDependency, ComponentsThatProvideNoServices_SortedLast)
{
// components providing no services
Component* c = m_entity->CreateComponent<ComponentC>(); // requires ServiceB
Component* p = m_entity->CreateComponent<ComponentP>();
// components providing a service
Component* b = m_entity->CreateComponent<ComponentB>();
Component* d = m_entity->CreateComponent<ComponentD>();
Component* i = m_entity->CreateComponent<ComponentI>();
Component* k = m_entity->CreateComponent<ComponentK>();
// the only dependency between these components is that C requires B
EXPECT_EQ(Entity::DependencySortResult::DSR_OK, m_entity->EvaluateDependencies());
const AZStd::vector<Component*>& components = m_entity->GetComponents();
const ptrdiff_t numComponents = m_entity->GetComponents().size();
ptrdiff_t maxIndexOfComponentProvidingServices = PTRDIFF_MIN;
for (Component* component : { b, d, i, k })
{
ptrdiff_t index = AZStd::distance(components.begin(), AZStd::find(components.begin(), components.end(), component));
EXPECT_TRUE(index >= 0 && index < numComponents);
maxIndexOfComponentProvidingServices = AZStd::max(maxIndexOfComponentProvidingServices, index);
}
ptrdiff_t minIndexOfComponentProvidingNoServices = PTRDIFF_MAX;
for (Component* component : { c, p })
{
ptrdiff_t index = AZStd::distance(components.begin(), AZStd::find(components.begin(), components.end(), component));
EXPECT_TRUE(index >= 0 && index < numComponents);
minIndexOfComponentProvidingNoServices = AZStd::min(minIndexOfComponentProvidingNoServices, index);
}
EXPECT_LT(maxIndexOfComponentProvidingServices, minIndexOfComponentProvidingNoServices);
}
// there was once a bug where we didn't check requirements if there was only 1 component
TEST_F(ComponentDependency, OneComponentRequiringService_FailsDueToMissingRequirements)
{
m_entity->CreateComponent<ComponentG>(); // requires ServiceH
EXPECT_EQ(Entity::DependencySortResult::MissingRequiredService, m_entity->EvaluateDependencies());
}
// there was once a bug where we didn't check requirements of components that provided no services
TEST_F(ComponentDependency, RequiringServiceWithoutProvidingService_FailsDueToMissingRequirements)
{
m_entity->CreateComponent<ComponentC>(); // requires ServiceB
m_entity->CreateComponent<ComponentC>(); // requires ServiceB
EXPECT_EQ(Entity::DependencySortResult::MissingRequiredService, m_entity->EvaluateDependencies());
// there was also once a bug where failed sorts would result in components vanishing
EXPECT_EQ(2, m_entity->GetComponents().size());
}
TEST_F(ComponentDependency, ComponentIncompatibleWithServiceItProvides_IsOkByItself)
{
m_entity->CreateComponent<ComponentI>(); // incompatible with ServiceI
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, TwoInstancesOfComponentIncompatibleWithServiceItProvides_AreIncompatible)
{
m_entity->CreateComponent<ComponentI>(); // incompatible with ServiceI
m_entity->CreateComponent<ComponentI>(); // incompatible with ServiceI
EXPECT_EQ(Entity::DependencySortResult::HasIncompatibleServices, m_entity->EvaluateDependencies());
}
// there was once a bug where failures due to cyclic dependencies would result in components vanishing
TEST_F(ComponentDependency, FailureDueToCyclicDependencies_LeavesComponentsInPlace)
{
m_entity->CreateComponent<ComponentG>(); // requires ServiceH
m_entity->CreateComponent<ComponentH>(); // requires ServiceG
EXPECT_EQ(Entity::DependencySortResult::HasCyclicDependency, m_entity->EvaluateDependencies());
// there was also once a bug where failed sorts would result in components vanishing
EXPECT_EQ(2, m_entity->GetComponents().size());
}
TEST_F(ComponentDependency, ComponentWithoutDescriptor_FailsDueToUnregisteredDescriptor)
{
CreateComponents_ABCDE();
// delete ComponentB's descriptor
ComponentDescriptorBus::Event(azrtti_typeid<ComponentB>(), &ComponentDescriptorBus::Events::ReleaseDescriptor);
EXPECT_EQ(Entity::DependencySortResult::DescriptorNotRegistered, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, StableSort_GetsSameResultsEveryTime)
{
// put a bunch of components on the entity
CreateComponents_ABCDE();
CreateComponents_ABCDE();
CreateComponents_ABCDE();
// throw in components whose dependencies could make the sort order ambiguous
m_entity->CreateComponent<ComponentI>(); // I is incompatible with itself, but depends on nothing
m_entity->CreateComponent<ComponentP>(); // P has no service declarations whatsoever
m_entity->CreateComponent<ComponentP>();
m_entity->CreateComponent<ComponentP>();
m_entity->CreateComponent<ComponentK>(); // K depends on J (but J not present)
m_entity->CreateComponent<ComponentK>();
m_entity->CreateComponent<ComponentK>();
// set Component IDs (using seeded random) so we get same results each time this test runs
u32 randSeed[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
Sfmt randGen(randSeed, AZ_ARRAY_SIZE(randSeed));
AZStd::unordered_map<Component*, ComponentId> componentIds;
for (Component* component : m_entity->GetComponents())
{
ComponentId id = randGen.Rand64();
componentIds[component] = id;
component->SetId(id);
}
// perform initial sort
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
const AZStd::vector<Component*> originalSortedOrder = m_entity->GetComponents();
// try shuffling the components a bunch of times
// we should always get the same sorted results
for (int iteration = 0; iteration < 50; ++iteration)
{
AZStd::vector<Component*> componentsToShuffle = m_entity->GetComponents();
// remove all components from entity
for (Component* component : componentsToShuffle)
{
m_entity->RemoveComponent(component);
}
// shuffle components
for (int i = 0; i < 200; ++i)
{
size_t swapA = randGen.Rand64() % componentsToShuffle.size();
size_t swapB = randGen.Rand64() % componentsToShuffle.size();
AZStd::swap(componentsToShuffle[swapA], componentsToShuffle[swapB]);
}
// put components back on entity
for (Component* component : componentsToShuffle)
{
m_entity->AddComponent(component);
// removing components resets their ID
// set it back to previous value so sort results are the same
component->SetId(componentIds[component]);
}
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
const AZStd::vector<Component*>& sorted = m_entity->GetComponents();
EXPECT_EQ(originalSortedOrder, sorted);
if (HasFailure())
{
break;
}
};
}
// Check that invalid user input, in the form of services accidentally listed multiple times,
// is handled appropriately and doesn't result in infinite loops.
TEST_F(ComponentDependency, ComponentAccidentallyProvidingSameServiceTwice_IsOk)
{
m_entity->CreateComponent<ComponentJ>(); // provides ServiceJ twice
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, DependingOnComponentWhichAccidentallyProvidesSameServiceTwice_IsOk)
{
m_entity->CreateComponent<ComponentJ>(); // provides ServiceJ twice
m_entity->CreateComponent<ComponentK>(); // depends on ServiceJ
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
EXPECT_EQ(azrtti_typeid<ComponentJ>(), azrtti_typeid(m_entity->GetComponents()[0]));
EXPECT_EQ(azrtti_typeid<ComponentK>(), azrtti_typeid(m_entity->GetComponents()[1]));
}
TEST_F(ComponentDependency, ComponentAccidentallyDependingOnSameServiceTwice_IsOk)
{
m_entity->CreateComponent<ComponentL>(); // depends on ServiceA twice
m_entity->CreateComponent<ComponentA>();
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
EXPECT_EQ(azrtti_typeid<ComponentA>(), azrtti_typeid(m_entity->GetComponents()[0]));
EXPECT_EQ(azrtti_typeid<ComponentL>(), azrtti_typeid(m_entity->GetComponents()[1]));
}
TEST_F(ComponentDependency, ComponentAccidentallyDependingAndRequiringSameService_IsOk)
{
m_entity->CreateComponent<ComponentM>(); // depends on ServiceA and requires Service A
m_entity->CreateComponent<ComponentA>();
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
EXPECT_EQ(azrtti_typeid<ComponentA>(), azrtti_typeid(m_entity->GetComponents()[0]));
EXPECT_EQ(azrtti_typeid<ComponentM>(), azrtti_typeid(m_entity->GetComponents()[1]));
}
TEST_F(ComponentDependency, ComponentAccidentallyListsIncompatibleServiceTwice_IsOkByItself)
{
m_entity->CreateComponent<ComponentN>(); // incompatible with ServiceA twice
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, ComponentAccidentallyListsIncompatibleServiceTwice_IncompatibilityStillDetected)
{
m_entity->CreateComponent<ComponentN>(); // incompatible with ServiceA twice
m_entity->CreateComponent<ComponentA>();
EXPECT_EQ(Entity::DependencySortResult::HasIncompatibleServices, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, ComponentAccidentallyListingIncompatibilityWithSelfTwice_IsOkByItself)
{
m_entity->CreateComponent<ComponentO>(); // incompatible with ServiceO twice
EXPECT_EQ(Entity::DependencySortResult::Success, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, TwoInstancesOfComponentAccidentallyListingIncompatibilityWithSelfTwice_AreIncompatible)
{
m_entity->CreateComponent<ComponentO>(); // incompatible with ServiceO twice
m_entity->CreateComponent<ComponentO>(); // incompatible with ServiceO twice
EXPECT_EQ(Entity::DependencySortResult::HasIncompatibleServices, m_entity->EvaluateDependencies());
}
/**
* UserSettingsComponent test
*/
class UserSettingsTestApp
: public ComponentApplication
, public UserSettingsFileLocatorBus::Handler
{
public:
void SetExecutableFolder(const char* path)
{
m_exeDirectory = path;
}
AZStd::string ResolveFilePath(u32 providerId) override
{
AZStd::string filePath;
if (providerId == UserSettings::CT_GLOBAL)
{
filePath = (m_exeDirectory / "GlobalUserSettings.xml").String();
}
else if (providerId == UserSettings::CT_LOCAL)
{
filePath = (m_exeDirectory / "LocalUserSettings.xml").String();
}
return filePath;
}
void SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations) override
{
ComponentApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("test");
specializations.Append("usersettingstest");
}
};
class MyUserSettings
: public UserSettings
{
public:
AZ_CLASS_ALLOCATOR(MyUserSettings, SystemAllocator, 0);
AZ_RTTI(MyUserSettings, "{ACC60C7B-60D8-4491-AD5D-42BA6656CC1F}", UserSettings);
static void Reflect(AZ::SerializeContext* sc)
{
sc->Class<MyUserSettings>()
->Field("intOption1", &MyUserSettings::m_intOption1);
}
int m_intOption1;
};
TEST(UserSettings, Test)
{
UserSettingsTestApp app;
//////////////////////////////////////////////////////////////////////////
// Create application environment code driven
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024;
Entity* systemEntity = app.Create(appDesc);
app.SetExecutableFolder(GetTestFolderPath().c_str());
app.UserSettingsFileLocatorBus::Handler::BusConnect();
// Make sure user settings file does not exist at this point
{
IO::SystemFile::Delete(app.ResolveFilePath(UserSettings::CT_GLOBAL).c_str());
IO::SystemFile::Delete(app.ResolveFilePath(UserSettings::CT_LOCAL).c_str());
}
MyUserSettings::Reflect(app.GetSerializeContext());
systemEntity->CreateComponent<MemoryComponent>();
UserSettingsComponent* globalUserSettingsComponent = systemEntity->CreateComponent<UserSettingsComponent>();
AZ_TEST_ASSERT(globalUserSettingsComponent);
globalUserSettingsComponent->SetProviderId(UserSettings::CT_GLOBAL);
UserSettingsComponent* localUserSettingsComponent = systemEntity->CreateComponent<UserSettingsComponent>();
AZ_TEST_ASSERT(localUserSettingsComponent);
localUserSettingsComponent->SetProviderId(UserSettings::CT_LOCAL);
systemEntity->Init();
systemEntity->Activate();
AZStd::intrusive_ptr<MyUserSettings> myGlobalUserSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_GLOBAL);
AZ_TEST_ASSERT(myGlobalUserSettings);
myGlobalUserSettings->m_intOption1 = 10;
AZStd::intrusive_ptr<MyUserSettings> storedGlobalSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_GLOBAL);
AZ_TEST_ASSERT(myGlobalUserSettings == storedGlobalSettings);
AZ_TEST_ASSERT(storedGlobalSettings->m_intOption1 == 10);
AZStd::intrusive_ptr<MyUserSettings> myLocalUserSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_LOCAL);
AZ_TEST_ASSERT(myLocalUserSettings);
myLocalUserSettings->m_intOption1 = 20;
AZStd::intrusive_ptr<MyUserSettings> storedLocalSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_LOCAL);
AZ_TEST_ASSERT(myLocalUserSettings == storedLocalSettings);
AZ_TEST_ASSERT(storedLocalSettings->m_intOption1 == 20);
// Deactivating will not trigger saving of user options, saving must be performed manually.
UserSettingsComponentRequestBus::Broadcast(&UserSettingsComponentRequests::Save);
systemEntity->Deactivate();
// Deactivate() should have cleared all the registered user options
storedGlobalSettings = UserSettings::Find<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_GLOBAL);
AZ_TEST_ASSERT(!storedGlobalSettings);
storedLocalSettings = UserSettings::Find<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_LOCAL);
AZ_TEST_ASSERT(!storedLocalSettings);
systemEntity->Activate();
// Verify that upon re-activation, we successfully loaded all settings saved during deactivation
storedGlobalSettings = UserSettings::Find<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_GLOBAL);
AZ_TEST_ASSERT(storedGlobalSettings);
myGlobalUserSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_GLOBAL);
AZ_TEST_ASSERT(myGlobalUserSettings == storedGlobalSettings);
AZ_TEST_ASSERT(storedGlobalSettings->m_intOption1 == 10);
storedLocalSettings = UserSettings::Find<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_LOCAL);
AZ_TEST_ASSERT(storedLocalSettings);
myLocalUserSettings = UserSettings::CreateFind<MyUserSettings>(AZ_CRC("MyUserSettings", 0x65286904), UserSettings::CT_LOCAL);
AZ_TEST_ASSERT(myLocalUserSettings == storedLocalSettings);
AZ_TEST_ASSERT(storedLocalSettings->m_intOption1 == 20);
myGlobalUserSettings = nullptr;
storedGlobalSettings = nullptr;
UserSettings::Release(myLocalUserSettings);
UserSettings::Release(storedLocalSettings);
app.Destroy();
//////////////////////////////////////////////////////////////////////////
}
class SimpleEntityRefTestComponent
: public Component
{
public:
AZ_COMPONENT(SimpleEntityRefTestComponent, "{ED4D3C2A-454D-47B0-B04E-9A26DC55D03B}");
SimpleEntityRefTestComponent(EntityId useId = EntityId())
: m_entityId(useId) {}
void Activate() override {}
void Deactivate() override {}
static void Reflect(ReflectContext* reflection)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<SimpleEntityRefTestComponent>()
->Field("entityId", &SimpleEntityRefTestComponent::m_entityId);
}
}
EntityId m_entityId;
};
class ComplexEntityRefTestComponent
: public Component
{
public:
AZ_COMPONENT(ComplexEntityRefTestComponent, "{BCCCD213-4A77-474C-B432-48DE6DB2FE4D}");
ComplexEntityRefTestComponent()
: m_entityIdHashMap(3) // create some buckets to make sure we distribute elements even when we have less than load factor
, m_entityIdHashSet(3) // create some buckets to make sure we distribute elements even when we have less than load factor
{
}
void Activate() override {}
void Deactivate() override {}
static void Reflect(ReflectContext* reflection)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ComplexEntityRefTestComponent>()
->Field("entityIds", &ComplexEntityRefTestComponent::m_entityIds)
->Field("entityIdHashMap", &ComplexEntityRefTestComponent::m_entityIdHashMap)
->Field("entityIdHashSet", &ComplexEntityRefTestComponent::m_entityIdHashSet)
->Field("entityId", &ComplexEntityRefTestComponent::m_entityIdIntMap);
}
}
AZStd::vector<EntityId> m_entityIds;
AZStd::unordered_map<EntityId, int> m_entityIdHashMap;
AZStd::unordered_set<EntityId> m_entityIdHashSet;
AZStd::map<EntityId, int> m_entityIdIntMap;
};
struct EntityIdRemapContainer
{
AZ_TYPE_INFO(EntityIdRemapContainer, "{63854212-37E9-480B-8E46-529682AB9EF7}");
AZ_CLASS_ALLOCATOR(EntityIdRemapContainer, AZ::SystemAllocator, 0);
static void Reflect(SerializeContext& serializeContext)
{
serializeContext.Class<EntityIdRemapContainer>()
->Field("Entity", &EntityIdRemapContainer::m_entity)
->Field("Id", &EntityIdRemapContainer::m_id)
->Field("otherId", &EntityIdRemapContainer::m_otherId)
;
}
AZ::Entity* m_entity;
AZ::EntityId m_id;
AZ::EntityId m_otherId;
};
TEST_F(Components, EntityUtilsTest)
{
EntityId id1 = Entity::MakeId();
{
EntityId id2 = Entity::MakeId();
EntityId id3 = Entity::MakeId();
EntityId id4 = Entity::MakeId();
EntityId id5 = Entity::MakeId();
SimpleEntityRefTestComponent testComponent1(id1);
SimpleEntityRefTestComponent testComponent2(id2);
SimpleEntityRefTestComponent testComponent3(id3);
Entity testEntity(id1);
testEntity.AddComponent(&testComponent1);
testEntity.AddComponent(&testComponent2);
testEntity.AddComponent(&testComponent3);
SerializeContext context;
const ComponentDescriptor* entityRefTestDescriptor = SimpleEntityRefTestComponent::CreateDescriptor();
entityRefTestDescriptor->Reflect(&context);
Entity::Reflect(&context);
unsigned int nReplaced = EntityUtils::ReplaceEntityRefs(
&testEntity
, [=](EntityId key, bool /*isEntityId*/) -> EntityId
{
if (key == id1)
{
return id4;
}
if (key == id2)
{
return id5;
}
return key;
}
, &context
);
AZ_TEST_ASSERT(nReplaced == 2);
AZ_TEST_ASSERT(testEntity.GetId() == id1);
AZ_TEST_ASSERT(testComponent1.m_entityId == id4);
AZ_TEST_ASSERT(testComponent2.m_entityId == id5);
AZ_TEST_ASSERT(testComponent3.m_entityId == id3);
testEntity.RemoveComponent(&testComponent1);
testEntity.RemoveComponent(&testComponent2);
testEntity.RemoveComponent(&testComponent3);
delete entityRefTestDescriptor;
}
// Test entity IDs replacement in special containers (that require update as a result of EntityId replacement)
{
// special crafted id, so we can change the hashing structure as
// we replace the entities ID
EntityId id2(1);
EntityId id3(13);
EntityId replace2(14);
EntityId replace3(3);
SerializeContext context;
const ComponentDescriptor* entityRefTestDescriptor = ComplexEntityRefTestComponent::CreateDescriptor();
entityRefTestDescriptor->Reflect(&context);
Entity::Reflect(&context);
ComplexEntityRefTestComponent testComponent1;
Entity testEntity(id1);
testEntity.AddComponent(&testComponent1);
// vector (baseline, it should not change, same with all other AZStd containers not tested below)
testComponent1.m_entityIds.push_back(id2);
testComponent1.m_entityIds.push_back(id3);
testComponent1.m_entityIds.push_back(EntityId(32));
// hash map
testComponent1.m_entityIdHashMap.insert(AZStd::make_pair(id2, 1));
testComponent1.m_entityIdHashMap.insert(AZStd::make_pair(id3, 2));
testComponent1.m_entityIdHashMap.insert(AZStd::make_pair(EntityId(32), 3));
testComponent1.m_entityIdHashMap.insert(AZStd::make_pair(EntityId(5), 4));
testComponent1.m_entityIdHashMap.insert(AZStd::make_pair(EntityId(16), 5));
// hash set
testComponent1.m_entityIdHashSet.insert(id2);
testComponent1.m_entityIdHashSet.insert(id3);
testComponent1.m_entityIdHashSet.insert(EntityId(32));
testComponent1.m_entityIdHashSet.insert(EntityId(5));
testComponent1.m_entityIdHashSet.insert(EntityId(16));
// map
testComponent1.m_entityIdIntMap.insert(AZStd::make_pair(id2, 1));
testComponent1.m_entityIdIntMap.insert(AZStd::make_pair(id3, 2));
testComponent1.m_entityIdIntMap.insert(AZStd::make_pair(EntityId(32), 3));
testComponent1.m_entityIdIntMap.insert(AZStd::make_pair(EntityId(5), 4));
testComponent1.m_entityIdIntMap.insert(AZStd::make_pair(EntityId(16), 5));
// set is currently not supported in the serializer, when implemented if it uses the same Associative container storage (which it should) it should just work
unsigned int nReplaced = EntityUtils::ReplaceEntityRefs(
&testEntity
, [=](EntityId key, bool /*isEntityId*/) -> EntityId
{
if (key == id2)
{
return replace2;
}
if (key == id3)
{
return replace3;
}
return key;
}
, &context
);
AZ_TEST_ASSERT(nReplaced == 8);
AZ_TEST_ASSERT(testEntity.GetId() == id1);
AZ_TEST_ASSERT(AZStd::find(testComponent1.m_entityIds.begin(), testComponent1.m_entityIds.end(), id2) == testComponent1.m_entityIds.end());
AZ_TEST_ASSERT(AZStd::find(testComponent1.m_entityIds.begin(), testComponent1.m_entityIds.end(), replace2) != testComponent1.m_entityIds.end());
AZ_TEST_ASSERT(AZStd::find(testComponent1.m_entityIds.begin(), testComponent1.m_entityIds.end(), replace3) != testComponent1.m_entityIds.end());
AZ_TEST_ASSERT(AZStd::find(testComponent1.m_entityIds.begin(), testComponent1.m_entityIds.end(), EntityId(32)) != testComponent1.m_entityIds.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashMap.find(id2) == testComponent1.m_entityIdHashMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashMap.find(replace2) != testComponent1.m_entityIdHashMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashMap.find(replace3) != testComponent1.m_entityIdHashMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashMap.find(EntityId(32)) != testComponent1.m_entityIdHashMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashSet.find(id2) == testComponent1.m_entityIdHashSet.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashSet.find(replace2) != testComponent1.m_entityIdHashSet.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashSet.find(replace3) != testComponent1.m_entityIdHashSet.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdHashSet.find(EntityId(32)) != testComponent1.m_entityIdHashSet.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdIntMap.find(id2) == testComponent1.m_entityIdIntMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdIntMap.find(replace2) != testComponent1.m_entityIdIntMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdIntMap.find(replace3) != testComponent1.m_entityIdIntMap.end());
AZ_TEST_ASSERT(testComponent1.m_entityIdIntMap.find(EntityId(32)) != testComponent1.m_entityIdIntMap.end());
testEntity.RemoveComponent(&testComponent1);
delete entityRefTestDescriptor;
}
}
// Temporary disabled. This will be re-enabled in the short term upon completion of SPEC-7384 and
// fixed in the long term upon completion of SPEC-4849
TEST_F(Components, DISABLED_EntityIdGeneration)
{
// Generate 1 million ids across 100 threads, and ensure that none collide
AZStd::concurrent_unordered_set<AZ::EntityId> entityIds;
auto GenerateIdThread = [&entityIds]()
{
for (size_t i = 0; i < AZ_TRAIT_UNIT_TEST_ENTITY_ID_GEN_TEST_COUNT; ++i)
{
EXPECT_TRUE(entityIds.insert(Entity::MakeId()));
}
};
//////////////////////////////////////////////////////////////////////////
// test generating EntityIDs from multiple threads
{
AZStd::vector<AZStd::thread> threads;
for (size_t i = 0; i < 100; ++i)
{
threads.emplace_back(GenerateIdThread);
}
for (AZStd::thread& thread : threads)
{
thread.join();
}
}
}
//=========================================================================
// Component Configuration
class ConfigurableComponentConfig : public ComponentConfig
{
public:
AZ_RTTI(ConfigurableComponentConfig, "{109C5A93-5571-4D45-BD2F-3938BF63AD83}", ComponentConfig);
int m_intVal = 0;
};
class ConfigurableComponent : public Component
{
public:
AZ_COMPONENT(ConfigurableComponent, "{E3103830-70F3-47AE-8F22-EF09BF3D57E9}");
static void Reflect(ReflectContext*) {}
int m_intVal = 0;
protected:
void Activate() override {}
void Deactivate() override {}
bool ReadInConfig(const ComponentConfig* baseConfig) override
{
if (auto config = azrtti_cast<const ConfigurableComponentConfig*>(baseConfig))
{
m_intVal = config->m_intVal;
return true;
}
return false;
}
bool WriteOutConfig(ComponentConfig* outBaseConfig) const override
{
if (auto config = azrtti_cast<ConfigurableComponentConfig*>(outBaseConfig))
{
config->m_intVal = m_intVal;
return true;
}
return false;
}
};
class UnconfigurableComponent : public Component
{
public:
AZ_COMPONENT(UnconfigurableComponent, "{772E3AA6-67AC-4655-B6C4-70BC45BAFD35}");
static void Reflect(ReflectContext*) {}
void Activate() override {}
void Deactivate() override {}
};
// fixture for testing ComponentConfig stuff
class ComponentConfiguration
: public Components
{
public:
void SetUp() override
{
Components::SetUp();
m_descriptors.emplace_back(ConfigurableComponent::CreateDescriptor());
m_descriptors.emplace_back(UnconfigurableComponent::CreateDescriptor());
}
void TearDown() override
{
m_descriptors.clear();
m_descriptors.set_capacity(0);
Components::TearDown();
}
AZStd::vector<AZStd::unique_ptr<ComponentDescriptor>> m_descriptors;
};
TEST_F(ComponentConfiguration, SetConfiguration_Succeeds)
{
ConfigurableComponentConfig config;
config.m_intVal = 5;
ConfigurableComponent component;
EXPECT_TRUE(component.SetConfiguration(config));
EXPECT_EQ(component.m_intVal, 5);
}
TEST_F(ComponentConfiguration, SetConfigurationOnActiveEntity_DoesNothing)
{
ConfigurableComponentConfig config;
config.m_intVal = 5;
Entity entity;
auto component = entity.CreateComponent<ConfigurableComponent>();
entity.Init();
entity.Activate();
EXPECT_EQ(Entity::State::Active, entity.GetState());
EXPECT_FALSE(component->SetConfiguration(config));
EXPECT_NE(component->m_intVal, 5);
}
TEST_F(ComponentConfiguration, SetWrongKindOfConfiguration_DoesNothing)
{
ComponentConfig config; // base config type
ConfigurableComponent component;
component.m_intVal = 19;
EXPECT_FALSE(component.SetConfiguration(config));
EXPECT_EQ(component.m_intVal, 19);
}
TEST_F(ComponentConfiguration, GetConfiguration_Succeeds)
{
ConfigurableComponent component;
component.m_intVal = 9;
ConfigurableComponentConfig config;
component.GetConfiguration(config);
EXPECT_EQ(component.m_intVal, 9);
}
TEST_F(ComponentConfiguration, SetConfigurationOnUnconfigurableComponent_Fails)
{
UnconfigurableComponent component;
ConfigurableComponentConfig config;
EXPECT_FALSE(component.SetConfiguration(config));
}
TEST_F(ComponentConfiguration, GetConfigurationOnUnconfigurableComponent_Fails)
{
UnconfigurableComponent component;
ConfigurableComponentConfig config;
EXPECT_FALSE(component.GetConfiguration(config));
}
//=========================================================================
TEST_F(Components, GenerateNewIdsAndFixRefsExistingMapTest)
{
SerializeContext context;
Entity::Reflect(&context);
EntityIdRemapContainer::Reflect(context);
const AZ::EntityId testId(21);
const AZ::EntityId nonMappedId(5465);
EntityIdRemapContainer testContainer1;
testContainer1.m_entity = aznew Entity(testId);
testContainer1.m_id = testId;
testContainer1.m_otherId = nonMappedId;
EntityIdRemapContainer clonedContainer;
context.CloneObjectInplace(clonedContainer, &testContainer1);
// Check cloned entity has same ids
EXPECT_NE(nullptr, clonedContainer.m_entity);
EXPECT_EQ(testContainer1.m_entity->GetId(), clonedContainer.m_entity->GetId());
EXPECT_EQ(testContainer1.m_id, clonedContainer.m_id);
EXPECT_EQ(testContainer1.m_otherId, clonedContainer.m_otherId);
// Generated new Ids in the testContainer store the results in the newIdMap
// The m_entity Entity id values should be remapped to a new value
AZStd::unordered_map<AZ::EntityId, AZ::EntityId> newIdMap;
EntityUtils::GenerateNewIdsAndFixRefs(&testContainer1, newIdMap, &context);
EXPECT_EQ(testContainer1.m_entity->GetId(), testContainer1.m_id);
EXPECT_NE(clonedContainer.m_entity->GetId(), testContainer1.m_entity->GetId());
EXPECT_NE(clonedContainer.m_id, testContainer1.m_id);
EXPECT_EQ(clonedContainer.m_otherId, testContainer1.m_otherId);
// Use the existing newIdMap to generate entityIds for the clonedContainer
// The testContainer1 and ClonedContainer should now have the same ids again
EntityUtils::GenerateNewIdsAndFixRefs(&clonedContainer, newIdMap, &context);
EXPECT_EQ(clonedContainer.m_entity->GetId(), clonedContainer.m_id);
EXPECT_EQ(testContainer1.m_entity->GetId(), clonedContainer.m_entity->GetId());
EXPECT_EQ(testContainer1.m_id, clonedContainer.m_id);
EXPECT_EQ(testContainer1.m_otherId, clonedContainer.m_otherId);
// Use a new map to generate entityIds for the clonedContainer
// The testContainer1 and ClonedContainer should have different ids again
AZStd::map<AZ::EntityId, AZ::EntityId> clonedIdMap; // Using regular map to test that different map types works with GenerateNewIdsAndFixRefs
EntityUtils::GenerateNewIdsAndFixRefs(&clonedContainer, clonedIdMap, &context);
EXPECT_EQ(clonedContainer.m_entity->GetId(), clonedContainer.m_id);
EXPECT_NE(testContainer1.m_entity->GetId(), clonedContainer.m_entity->GetId());
EXPECT_NE(testContainer1.m_id, clonedContainer.m_id);
EXPECT_EQ(testContainer1.m_otherId, clonedContainer.m_otherId);
delete testContainer1.m_entity;
delete clonedContainer.m_entity;
}
//=========================================================================
// Component Configuration versioning
// Version 1 of a configuration for a HydraComponent
class HydraConfigV1
: public ComponentConfig
{
public:
AZ_RTTI(HydraConfigV1, "{02198FDB-5CDB-4983-BC0B-CF1AA20FF2AF}", ComponentConfig);
int m_numHeads = 1;
};
// To add fields, inherit from previous version.
class HydraConfigV2
: public HydraConfigV1
{
public:
AZ_RTTI(HydraConfigV2, "{BC68C167-6B01-489C-8415-626455670C34}", HydraConfigV1);
int m_numArms = 2; // now the hydra has multiple arms, as well as multiple heads
};
// To make a breaking change, start from scratch by inheriting from base ComponentConfig.
class HydraConfigV3
: public ComponentConfig
{
public:
AZ_RTTI(HydraConfigV3, "{71C41829-AA51-4179-B8B4-3C278CBB26AA}", ComponentConfig);
int m_numHeads = 1;
int m_numArmsPerHead = 2; // now we require each head to have the same number of arms
};
// A component with many heads, and many arms
class HydraComponent
: public Component
{
public:
AZ_RTTI(HydraComponent, "", Component);
AZ_CLASS_ALLOCATOR(HydraComponent, AZ::SystemAllocator, 0);
// serialized data
HydraConfigV3 m_config;
// runtime data
int m_numArms;
HydraComponent() = default;
void Activate() override
{
m_numArms = m_config.m_numHeads * m_config.m_numArmsPerHead;
}
void Deactivate() override {}
bool ReadInConfig(const ComponentConfig* baseConfig) override
{
if (auto v1 = azrtti_cast<const HydraConfigV1*>(baseConfig))
{
m_config.m_numHeads = v1->m_numHeads;
// v2 is based on v1
if (auto v2 = azrtti_cast<const HydraConfigV2*>(v1))
{
// v2 let user specify the total number of arms, but now we force each head to have same number of arms
if (v2->m_numHeads <= 0)
{
m_config.m_numArmsPerHead = 0;
}
else
{
m_config.m_numArmsPerHead = v2->m_numArms / v2->m_numHeads;
}
}
else
{
// v1 assumed 2 arms per head
m_config.m_numArmsPerHead = 2;
}
return true;
}
if (auto v3 = azrtti_cast<const HydraConfigV3*>(baseConfig))
{
m_config = *v3;
return true;
}
return false;
}
bool WriteOutConfig(ComponentConfig* outBaseConfig) const override
{
if (auto v1 = azrtti_cast<HydraConfigV1*>(outBaseConfig))
{
v1->m_numHeads = m_config.m_numHeads;
// v2 is based on v1
if (auto v2 = azrtti_cast<HydraConfigV2*>(v1))
{
v2->m_numArms = m_config.m_numHeads * m_config.m_numArmsPerHead;
}
return true;
}
if (auto v3 = azrtti_cast<HydraConfigV3*>(outBaseConfig))
{
*v3 = m_config;
return true;
}
return false;
}
};
TEST_F(Components, SetConfigurationV1_Succeeds)
{
HydraConfigV1 config;
config.m_numHeads = 3;
HydraComponent component;
EXPECT_TRUE(component.SetConfiguration(config));
EXPECT_EQ(component.m_config.m_numHeads, 3);
}
TEST_F(Components, GetConfigurationV1_Succeeds)
{
HydraConfigV1 config;
HydraComponent component;
component.m_config.m_numHeads = 8;
EXPECT_TRUE(component.GetConfiguration(config));
EXPECT_EQ(config.m_numHeads, component.m_config.m_numHeads);
}
TEST_F(Components, SetConfigurationV2_Succeeds)
{
HydraConfigV2 config;
config.m_numHeads = 4;
config.m_numArms = 12;
HydraComponent component;
EXPECT_TRUE(component.SetConfiguration(config));
EXPECT_EQ(component.m_config.m_numHeads, config.m_numHeads);
EXPECT_EQ(component.m_config.m_numArmsPerHead, 3);
}
TEST_F(Components, GetConfigurationV2_Succeeds)
{
HydraConfigV2 config;
HydraComponent component;
component.m_config.m_numHeads = 12;
component.m_config.m_numArmsPerHead = 1;
EXPECT_TRUE(component.GetConfiguration(config));
EXPECT_EQ(config.m_numHeads, component.m_config.m_numHeads);
EXPECT_EQ(config.m_numArms, 12);
}
TEST_F(Components, SetConfigurationV3_Succeeds)
{
HydraConfigV3 config;
config.m_numHeads = 2;
config.m_numArmsPerHead = 4;
HydraComponent component;
EXPECT_TRUE(component.SetConfiguration(config));
EXPECT_EQ(component.m_config.m_numHeads, config.m_numHeads);
EXPECT_EQ(component.m_config.m_numArmsPerHead, config.m_numArmsPerHead);
}
TEST_F(Components, GetConfigurationV3_Succeeds)
{
HydraConfigV3 config;
HydraComponent component;
component.m_config.m_numHeads = 94;
component.m_config.m_numArmsPerHead = 18;
EXPECT_TRUE(component.GetConfiguration(config));
EXPECT_EQ(config.m_numHeads, component.m_config.m_numHeads);
EXPECT_EQ(config.m_numArmsPerHead, component.m_config.m_numArmsPerHead);
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_EmptyList_ReturnsFalse)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
const ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
const bool servicesRemoved = EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr);
EXPECT_FALSE(servicesRemoved);
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_OnlyOneService_ReturnsFalse)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
const ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
const bool servicesRemoved = EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr);
EXPECT_FALSE(servicesRemoved);
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_NoDuplicates_ReturnsFalse)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("AnotherService"));
dependencyList.push_back(AZ_CRC("YetAnotherService"));
for (ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
dependencyIter != dependencyList.end();
++dependencyIter)
{
const bool servicesRemoved = EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr);
EXPECT_FALSE(servicesRemoved);
}
// Make sure no services were removed.
EXPECT_EQ(dependencyList.size(), 3);
EXPECT_EQ(dependencyList[0], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[1], AZ_CRC("AnotherService"));
EXPECT_EQ(dependencyList[2], AZ_CRC("YetAnotherService"));
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_DuplicateAfterIterator_ReturnsTrueClearsDuplicates)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("AnotherService"));
dependencyList.push_back(AZ_CRC("YetAnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
EXPECT_TRUE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_EQ(dependencyIter, dependencyList.end());
// Make sure the service was removed.
EXPECT_EQ(dependencyList.size(), 3);
EXPECT_EQ(dependencyList[0], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[1], AZ_CRC("AnotherService"));
EXPECT_EQ(dependencyList[2], AZ_CRC("YetAnotherService"));
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_2DuplicatesAfterIterator_ReturnsTrueClearsDuplicates)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("AnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("YetAnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
EXPECT_TRUE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_EQ(dependencyIter, dependencyList.end());
// Make sure the service was removed.
EXPECT_EQ(dependencyList.size(), 3);
EXPECT_EQ(dependencyList[0], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[1], AZ_CRC("AnotherService"));
EXPECT_EQ(dependencyList[2], AZ_CRC("YetAnotherService"));
}
// The duplicate check logic only checks after the current iterator for performance reasons.
// This function is primarily used in loops that are already iterating over the service dependencies.
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_DuplicateBeforeIterator_ReturnsFalseDuplicateRemains)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("AnotherService"));
dependencyList.push_back(AZ_CRC("YetAnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
// Skip the first element to leave a duplicate before the iterator.
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_EQ(dependencyIter, dependencyList.end());
// Make sure the service was not removed.
EXPECT_EQ(dependencyList.size(), 4);
EXPECT_EQ(dependencyList[0], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[1], AZ_CRC("AnotherService"));
EXPECT_EQ(dependencyList[2], AZ_CRC("YetAnotherService"));
EXPECT_EQ(dependencyList[3], AZ_CRC("SomeService"));
}
TEST_F(Components, RemoveDuplicateServicesOfAndAfterIterator_DuplicateBeforeAndAfterIterator_ReturnsTrueListUpdated)
{
AZ::ComponentDescriptor::DependencyArrayType dependencyList;
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("AnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
dependencyList.push_back(AZ_CRC("YetAnotherService"));
dependencyList.push_back(AZ_CRC("SomeService"));
ComponentDescriptor::DependencyArrayType::iterator dependencyIter = dependencyList.begin();
// Skip the first element to leave a duplicate before the iterator.
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_TRUE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_FALSE(EntityUtils::RemoveDuplicateServicesOfAndAfterIterator(dependencyIter, dependencyList, nullptr));
++dependencyIter;
EXPECT_EQ(dependencyIter, dependencyList.end());
// Make sure one service was removed, and another not removed.
EXPECT_EQ(dependencyList.size(), 4);
EXPECT_EQ(dependencyList[0], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[1], AZ_CRC("AnotherService"));
EXPECT_EQ(dependencyList[2], AZ_CRC("SomeService"));
EXPECT_EQ(dependencyList[3], AZ_CRC("YetAnotherService"));
}
} // namespace UnitTest
#if defined(HAVE_BENCHMARK)
namespace Benchmark
{
static void BM_ComponentDependencySort(::benchmark::State& state)
{
// descriptors are cleaned up when ComponentApplication shuts down
aznew UnitTest::ComponentADescriptor;
aznew UnitTest::ComponentB::DescriptorType;
aznew UnitTest::ComponentC::DescriptorType;
aznew UnitTest::ComponentD::DescriptorType;
aznew UnitTest::ComponentE::DescriptorType;
aznew UnitTest::ComponentE2::DescriptorType;
ComponentApplication componentApp;
ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
ComponentApplication::StartupParameters startupParams;
startupParams.m_allocator = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
Entity* systemEntity = componentApp.Create(desc, startupParams);
systemEntity->Init();
while(state.KeepRunning())
{
// create components to sort
state.PauseTiming();
AZStd::vector<Component*> components;
AZ_Assert((state.range(0) % 6) == 0, "Multiple of 6 required");
while ((int)components.size() < state.range(0))
{
components.push_back(aznew UnitTest::ComponentA());
components.push_back(aznew UnitTest::ComponentB());
components.push_back(aznew UnitTest::ComponentC());
components.push_back(aznew UnitTest::ComponentD());
components.push_back(aznew UnitTest::ComponentE());
components.push_back(aznew UnitTest::ComponentE2());
}
state.ResumeTiming();
// do sort
Entity::DependencySortOutcome outcome = Entity::DependencySort(components);
// cleanup
state.PauseTiming();
AZ_Assert(outcome.IsSuccess(), "Sort failed");
for (Component* component : components)
{
delete component;
}
state.ResumeTiming();
}
}
BENCHMARK(BM_ComponentDependencySort)->Arg(6)->Arg(60);
} // Benchmark
#endif // HAVE_BENCHMARK