5fc4551ac0
* Enable relocation of the Project Game Release Layout Relocating the Project Game Release Layout to another directory on the file system failed due to the querying of the engine root failing due to the ComponentApplication::m_engineRoot not using the project path stored in the SettingsRegisry if the engine root cannot be detected Removed the ApplicationRequestBus GetEngineRoot function. The ComponentApplicationRequestBus has a function of the same name that returns the same path. Removed the deprecated GetAppRoot function. The path it returns has no defined value. It was not the engine root or the project root. Removed unused CFileUtil and CFileUtil_impl functions that were invoking the ApplicationREquestBus GetEngineRoot function. On the way to update the functions it was discovered that they aren't called Added a CalculateBranchToken overload that can populate a fixed_string to avoid heap allocations Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Protect against an empty list of artifacts to remove when generating the engine.pak Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2023 lines
83 KiB
C++
2023 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/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:
|
|
AZStd::string ResolveFilePath(u32 providerId) override
|
|
{
|
|
AZStd::string filePath;
|
|
if (providerId == UserSettings::CT_GLOBAL)
|
|
{
|
|
filePath = (AZ::IO::Path(GetTestFolderPath()) / "GlobalUserSettings.xml").Native();
|
|
}
|
|
else if (providerId == UserSettings::CT_LOCAL)
|
|
{
|
|
filePath = (AZ::IO::Path(GetTestFolderPath()) / "LocalUserSettings.xml").Native();
|
|
}
|
|
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.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
|