Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,132 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "EntityOwnershipServiceTestFixture.h"
#include <AzCore/UserSettings/UserSettingsComponent.h>
namespace UnitTest
{
void EntityOwnershipServiceTestFixture::SetUpEntityOwnershipServiceTest()
{
AllocatorsTestFixture::SetUp();
AZ::ComponentApplication::Descriptor componentApplicationDescriptor;
componentApplicationDescriptor.m_useExistingAllocator = true;
componentApplicationDescriptor.m_enableDrilling = false; // we already created a memory driller for the test(AllocatorsTestFixture)
m_app = AZStd::make_unique<EntityOwnershipServiceApplication>();
m_app->Start(componentApplicationDescriptor);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
void EntityOwnershipServiceTestFixture::TearDownEntityOwnershipServiceTest()
{
m_app.reset();
AllocatorsTestFixture::TearDown();
}
AZ::ComponentTypeList EntityOwnershipServiceTestFixture::EntityOwnershipServiceApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList defaultRequiredComponents = AzFramework::Application::GetRequiredSystemComponents();
auto findComponentIterator = AZStd::find(defaultRequiredComponents.begin(), defaultRequiredComponents.end(),
azrtti_typeid<AzFramework::GameEntityContextComponent>());
if (findComponentIterator != defaultRequiredComponents.end())
{
defaultRequiredComponents.erase(findComponentIterator);
}
return defaultRequiredComponents;
}
AzFramework::RootSliceAsset EntityOwnershipServiceTestFixture::GetRootSliceAsset()
{
AzFramework::RootSliceAsset rootSliceAsset;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSliceAsset,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootAsset);
return rootSliceAsset;
}
void EntityOwnershipServiceTestFixture::HandleEntitiesAdded(const AzFramework::EntityList& entityList)
{
m_entitiesAddedCallbackTriggered = true;
for (AZ::Entity* entity : entityList)
{
// If the entities are not initialized, they won't be removed from ComponentApplication during Entity destruction.
if (entity->GetState() != AZ::Entity::State::Init)
{
entity->Init();
}
}
}
void EntityOwnershipServiceTestFixture::HandleEntitiesRemoved(const AzFramework::EntityIdList&)
{
m_entityRemovedCallbackTriggered = true;
}
bool EntityOwnershipServiceTestFixture::ValidateEntities(const AzFramework::EntityList&)
{
m_validateEntitiesCallbackTriggered = true;
return m_areEntitiesValidForContext;
}
AzFramework::SliceInstantiationTicket EntityOwnershipServiceTestFixture::AddSlice(const EntityList& entityList,
const bool isAsynchronous)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
return AddSlice(entityList, isAsynchronous, sliceAsset);
}
AzFramework::SliceInstantiationTicket EntityOwnershipServiceTestFixture::AddSlice(const EntityList& entityList,
const bool isAsynchronous, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset)
{
AddSliceComponentToAsset(sliceAsset, entityList);
AzFramework::SliceInstantiationTicket sliceInstantiationTicket;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(sliceInstantiationTicket,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::InstantiateSlice, sliceAsset, nullptr, nullptr);
if (!isAsynchronous)
{
AZ::TickBus::ExecuteQueuedEvents();
}
return sliceInstantiationTicket;
}
void EntityOwnershipServiceTestFixture::AddEditorSlice(
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const AZ::Transform& worldTransform, const EntityList& entityList)
{
AddSliceComponentToAsset(sliceAsset, entityList);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, sliceAsset, worldTransform);
AZ::TickBus::ExecuteQueuedEvents();
}
void EntityOwnershipServiceTestFixture::AddSliceComponentToAsset(AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
const EntityList& entityList)
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
for (AZ::Entity* entity : entityList)
{
sliceComponent->AddEntity(entity);
}
sliceAsset->SetData(sliceEntity, sliceComponent);
}
}
@@ -0,0 +1,63 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/Entity/SliceEntityOwnershipService.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h>
namespace UnitTest
{
using EntityList = AZStd::vector<AZ::Entity*>;
class EntityOwnershipServiceTestFixture
: public AllocatorsTestFixture
{
protected:
AzFramework::RootSliceAsset GetRootSliceAsset();
void SetUpEntityOwnershipServiceTest();
void TearDownEntityOwnershipServiceTest();
AzFramework::SliceInstantiationTicket AddSlice(const EntityList& entityList, const bool isAsynchronous = false);
void AddEditorSlice(
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const AZ::Transform& worldTransform, const EntityList& entityList);
AzFramework::SliceInstantiationTicket AddSlice(const EntityList& entityList, const bool isAsynchronous,
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset);
void HandleEntitiesAdded(const AzFramework::EntityList& entityList);
void HandleEntitiesRemoved(const AzFramework::EntityIdList& entityIds);
bool ValidateEntities(const AzFramework::EntityList&);
void AddSliceComponentToAsset(AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const EntityList& entityList);
AZStd::unique_ptr<AzFramework::Application> m_app;
bool m_entitiesAddedCallbackTriggered = false;
bool m_entityRemovedCallbackTriggered = false;
bool m_validateEntitiesCallbackTriggered = false;
bool m_areEntitiesValidForContext = true;
class EntityOwnershipServiceApplication : public AzToolsFramework::ToolsApplication
{
public:
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
};
}
@@ -0,0 +1,271 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h>
#include "EntityOwnershipServiceTestFixture.h"
namespace UnitTest
{
class SliceEditorEntityOwnershipTests
: public EntityOwnershipServiceTestFixture
{
public:
void SetUp() override
{
SetUpEntityOwnershipServiceTest();
m_sliceEditorEntityOwnershipService = AZStd::make_unique<AzToolsFramework::SliceEditorEntityOwnershipService>(
AZ::Uuid::CreateNull(), m_app->GetSerializeContext());
m_sliceEditorEntityOwnershipService->SetEntitiesAddedCallback([this](const AzFramework::EntityList& entityList)
{
this->HandleEntitiesAdded(entityList);
});
m_sliceEditorEntityOwnershipService->SetEntitiesRemovedCallback([this](const AzFramework::EntityIdList& entityIds)
{
this->HandleEntitiesRemoved(entityIds);
});
m_sliceEditorEntityOwnershipService->SetValidateEntitiesCallback([this](const AzFramework::EntityList& entityList)
{
return this->ValidateEntities(entityList);
});
m_sliceEditorEntityOwnershipService->Initialize();
}
void TearDown() override
{
m_sliceEditorEntityOwnershipService->Destroy();
m_sliceEditorEntityOwnershipService.reset();
TearDownEntityOwnershipServiceTest();
}
protected:
AZStd::unique_ptr<AzToolsFramework::SliceEditorEntityOwnershipService> m_sliceEditorEntityOwnershipService;
};
TEST_F(SliceEditorEntityOwnershipTests, Initialize_ResetOwnershipService_CreateRootSlice)
{
m_sliceEditorEntityOwnershipService->Reset();
EXPECT_TRUE(GetRootSliceAsset()->GetComponent() != nullptr);
}
TEST_F(SliceEditorEntityOwnershipTests, OnAssetReloaded_RootAssetReloaded_ReloadEntities)
{
// Clone the root slice asset
AZ::Data::Asset<AZ::SliceAsset> rootSliceAssetClone(GetRootSliceAsset().Get()->Clone(), AZ::Data::AssetLoadBehavior::Default);
AZ::Entity* sliceRootEntity = new AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceRootEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
sliceComponent->AddEntity(aznew AZ::Entity("testEntity"));
rootSliceAssetClone->SetData(sliceRootEntity, sliceComponent);
m_sliceEditorEntityOwnershipService->OnAssetReloaded(rootSliceAssetClone);
// Validate that entities-added callback is triggerted.
EXPECT_TRUE(m_entitiesAddedCallbackTriggered);
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Validate that there is only one entity under root slice.
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_EQ(entitiesUnderRootSlice.at(0)->GetName(), "testEntity");
}
TEST_F(SliceEditorEntityOwnershipTests, LoadFromStream_RemapIdsFalse_IdsNotRemapped)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZStd::vector<char> charBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&charBuffer);
AZ::Utils::SaveObjectToStream<AZ::Entity>(stream, AZ::ObjectStream::ST_XML, rootEntity, m_app->GetSerializeContext());
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
EXPECT_TRUE(m_sliceEditorEntityOwnershipService->LoadFromStream(stream, false));
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(previousToNewIdMap,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetLoadedEntityIdMap);
// Verify that remapping of entityIds is not done by comparing the entityIds in previousToNewIdMap
EXPECT_TRUE(previousToNewIdMap.begin()->first == previousToNewIdMap.begin()->second);
delete rootEntity;
}
TEST_F(SliceEditorEntityOwnershipTests, InstantiateEditorSlice_ValidAssetProvided_SliceCreated)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{});
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
// Verify that the created slice has the same asset as the one it's provided to be created with.
EXPECT_EQ(sliceAsset, slicesUnderRootSlice.front().GetSliceAsset());
}
TEST_F(SliceEditorEntityOwnershipTests, PromoteEditorEntitiesIntoSlice_ValidEntitiesProvided_SliceCreated)
{
AZ::Entity* looseEntity = aznew AZ::Entity("testEntity");
m_sliceEditorEntityOwnershipService->AddEntity(looseEntity);
AZ::Entity* entityInSlice = aznew AZ::Entity("testEntity");
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddSliceComponentToAsset(sliceAsset, EntityList{ entityInSlice });
AZ::SliceComponent::EntityIdToEntityIdMap looseEntityIdToSliceAssetEntityIdMap;
looseEntityIdToSliceAssetEntityIdMap.emplace(looseEntity->GetId(), entityInSlice->GetId());
// Verify that no slices exist.
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 0);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::PromoteEditorEntitiesIntoSlice,
sliceAsset, looseEntityIdToSliceAssetEntityIdMap);
// Verify that one slice is created.
slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
// Verify that there exists one slice instance with one entity and the correct slice asset.
ASSERT_EQ(sliceAsset, sliceReference.GetSliceAsset());
ASSERT_EQ(sliceReference.GetInstances().size(), 1);
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Verify that the entity in the created slice has the same id of the provided test entity.
EXPECT_EQ(entitiesOfSlice[0]->GetId(), looseEntity->GetId());
}
TEST_F(SliceEditorEntityOwnershipTests, DetachSliceEntities_ValidEntitiesProvided_EntitiesDetached)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* entityInSlice = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ entityInSlice });
// Verify that one slice is created and it has one editor entity
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Verify that owning slice for the editor entity exists.
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddressBeforeDetach;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddressBeforeDetach, entitiesOfSlice[0]->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
EXPECT_TRUE(sliceInstanceAddressBeforeDetach.IsValid());
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::DetachSliceEntities,
AzToolsFramework::EntityIdList{ entitiesOfSlice[0]->GetId() });
// Verify that owning slice for the editor entity doesn't exist.
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddressAfterDetach;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddressAfterDetach, entitiesOfSlice[0]->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
EXPECT_FALSE(sliceInstanceAddressAfterDetach.IsValid());
}
TEST_F(SliceEditorEntityOwnershipTests, DetachSliceInstances_ValidInstanceProvided_InstanceDetached)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ testEntity });
// Verify that one slice exists before detaching it.
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 1);
// Verify that there are no loose entities in the editor.
EntityList looseEntitiesBeforeDetach;
m_sliceEditorEntityOwnershipService->GetNonPrefabEntities(looseEntitiesBeforeDetach);
EXPECT_TRUE(looseEntitiesBeforeDetach.size() == 0);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
auto sliceInstanceIterator = sliceReference.GetInstances().begin();
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress(&sliceReference, &(*sliceInstanceIterator));
// Verify that there is one entity in the slice that is about to be detached.
EntityList entitiesInsliceBeforeDetach = sliceInstanceIterator->GetInstantiated()->m_entities;
EXPECT_TRUE(entitiesInsliceBeforeDetach.size() == 1);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::DetachSliceInstances,
AZ::SliceComponent::SliceInstanceAddressSet{ sliceInstanceAddress });
// Verify that the only slice that existed is not there anymore after detaching it.
slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
EXPECT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 0);
// Verify that that the detached slice entity is now a loose entity in the editor.
EntityList looseEntitiesAfterDetach;
m_sliceEditorEntityOwnershipService->GetNonPrefabEntities(looseEntitiesAfterDetach);
EXPECT_TRUE(looseEntitiesAfterDetach.size() == 1);
EXPECT_EQ(entitiesInsliceBeforeDetach[0]->GetId(), looseEntitiesAfterDetach[0]->GetId());
}
TEST_F(SliceEditorEntityOwnershipTests, RestoreSliceEntity_SliceEntityDeleted_SliceEntityRestored)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ testEntity });
// Verify that one slice exists
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
// Verify that one entity exists in the slice
EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Get the slice entity ancestor and slice instance id before destroying the entity of the slice
AZ::SliceComponent::EntityAncestorList entityAncestorList;
sliceReference.GetInstanceEntityAncestry(entitiesOfSlice.front()->GetId(), entityAncestorList);
AZ::SliceComponent::SliceInstanceId sliceInstanceId = sliceReference.GetInstances().begin()->GetId();
m_sliceEditorEntityOwnershipService->DestroyEntityById(entitiesOfSlice.front()->GetId());
// Verify that no slices exists after slice entity is destroyed.
ASSERT_EQ(slicesUnderRootSlice.size(), 0);
// Restore the slice entity
AZ::SliceComponent::EntityRestoreInfo entityRestoreInfo = AZ::SliceComponent::EntityRestoreInfo(sliceAsset,
sliceInstanceId, entityAncestorList.front().m_entity->GetId(), AZ::DataPatch::FlagsMap{});
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::RestoreSliceEntity, entitiesOfSlice.front(),
entityRestoreInfo, AzToolsFramework::SliceEntityRestoreType::Deleted);
AZ::TickBus::ExecuteQueuedEvents();
// Verify that slice is restored with the same entity it had before.
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
EntityList entitiesOfSliceAfterRestore = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSliceAfterRestore.size(), 1);
EXPECT_EQ(entitiesOfSliceAfterRestore.front()->GetId(), entitiesOfSlice.front()->GetId());
}
}
@@ -0,0 +1,411 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Entity/SliceEntityOwnershipService.h>
#include "EntityOwnershipServiceTestFixture.h"
#include <AzToolsFramework/Slice/SliceMetadataEntityContextBus.h>
namespace UnitTest
{
class SliceEntityOwnershipTests
: public EntityOwnershipServiceTestFixture
{
public:
void SetUp() override
{
SetUpEntityOwnershipServiceTest();
m_sliceEntityOwnershipService = AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(AZ::Uuid::CreateNull(),
m_app->GetSerializeContext());
m_sliceEntityOwnershipService->Initialize();
m_sliceEntityOwnershipService->SetEntitiesAddedCallback([this](const AzFramework::EntityList& entityList)
{
this->HandleEntitiesAdded(entityList);
});
m_sliceEntityOwnershipService->SetEntitiesRemovedCallback([this](const AzFramework::EntityIdList& entityIds)
{
this->HandleEntitiesRemoved(entityIds);
});
m_sliceEntityOwnershipService->SetValidateEntitiesCallback([this](const AzFramework::EntityList& entityList)
{
return this->ValidateEntities(entityList);
});
}
void TearDown() override
{
m_sliceEntityOwnershipService->SetEntitiesAddedCallback(nullptr);
// In order for the death tests to work, we have to destroy the EOS early. So, don't try to destroy again.
if (m_sliceEntityOwnershipService->IsInitialized())
{
m_sliceEntityOwnershipService->Destroy();
}
m_sliceEntityOwnershipService.reset();
TearDownEntityOwnershipServiceTest();
}
protected:
AZStd::unique_ptr<AzFramework::SliceEntityOwnershipService> m_sliceEntityOwnershipService;
};
using SliceEntityOwnershipDeathTests = SliceEntityOwnershipTests;
TEST_F(SliceEntityOwnershipTests, AddEntity_InitalizedCorrectly_EntityCreated)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
// Validate that entities-added callback is triggerted.
EXPECT_TRUE(m_entitiesAddedCallbackTriggered);
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Validate that there is only one entity under root slice.
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_EQ(entitiesUnderRootSlice.at(0)->GetName(), "testEntity");
}
TEST_F(SliceEntityOwnershipTests, DestroyEntityById_EntityAdded_EntityDestroyed)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
AzFramework::EntityList entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Verify that entity is added
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_TRUE(m_sliceEntityOwnershipService->DestroyEntityById(testEntity->GetId()));
entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Verify that entity is destroyed
EXPECT_EQ(entitiesUnderRootSlice.size(), 0);
}
TEST_F(SliceEntityOwnershipTests, GetRootSlice_RootAssetAbsent_ReturnNull)
{
m_sliceEntityOwnershipService->Destroy();
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
EXPECT_EQ(rootSlice, nullptr);
}
TEST_F(SliceEntityOwnershipTests, GetRootSlice_RootAssetPresent_ReturnRootSlice)
{
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
EXPECT_NE(rootSlice, nullptr);
}
TEST_F(SliceEntityOwnershipTests, Reset_SliceAdded_DestroySliceEntities)
{
AzFramework::EntityList entitiesToAdd = AzFramework::EntityList{ aznew AZ::Entity() };
AddSlice(entitiesToAdd);
size_t slicesCountBeforeReset = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// Verify that slice exists
EXPECT_EQ(slicesCountBeforeReset, 1);
m_sliceEntityOwnershipService->Reset();
size_t slicesCountAfterReset = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// Verify that slices under rootSlice were removed after reset of EntityOwnershipService.
EXPECT_EQ(slicesCountAfterReset, 0);
// Verify that call to destroy entities in the added slice occured.
EXPECT_TRUE(m_entityRemovedCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, Reset_SliceInstantiationStarted_StopSliceInstantiation)
{
AddSlice(AzFramework::EntityList{}, true);
m_sliceEntityOwnershipService->Reset();
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, Reset_EntityAdded_EntityDestroyedAfterReset)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
m_sliceEntityOwnershipService->Reset();
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
EXPECT_EQ(entitiesUnderRootSlice.size(), 0);
EXPECT_TRUE(m_entityRemovedCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_NoRootEntity_FailToLoadEntity)
{
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, nullptr, false, nullptr);
EXPECT_FALSE(rootEntityLoadSuccessful);
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_NoSliceComponent_FailToLoadEntity)
{
AZ::Entity* testEntity = aznew AZ::Entity();
// Suppress the AZ_Error thrown for not creating the root slice.
AZ_TEST_START_TRACE_SUPPRESSION;
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, testEntity, false, nullptr);
EXPECT_FALSE(rootEntityLoadSuccessful);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
delete testEntity;
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_RemapIdsTrue_IdsRemapped)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
previousToNewIdMap.emplace(testEntity->GetId(), testEntity->GetId());
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream,
rootEntity, true, &previousToNewIdMap);
EXPECT_TRUE(rootEntityLoadSuccessful);
// Verify that remapping of entityIds is done by comparing the entityIds in previousToNewIdMap
EXPECT_TRUE(previousToNewIdMap.begin()->first != previousToNewIdMap.begin()->second);
}
TEST_F(SliceEntityOwnershipTests, FindLoadedEntityIdMapping_IdsNotRemapped_EntityIdPresent)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, rootEntity, false, nullptr);
EXPECT_TRUE(rootEntityLoadSuccessful);
AZ::EntityId loadedEntityId;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(loadedEntityId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::FindLoadedEntityIdMapping, testEntity->GetId());
// Verify that the entityId in the loadedEntityIdMap is same as the provided entityId, which happens when remapping is not done.
EXPECT_TRUE(loadedEntityId == testEntity->GetId());
}
TEST_F(SliceEntityOwnershipTests, FindLoadedEntityIdMapping_IdsRemapped_EntityIdAbsent)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
previousToNewIdMap.emplace(testEntity->GetId(), testEntity->GetId());
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream,
rootEntity, true, &previousToNewIdMap);
EXPECT_TRUE(rootEntityLoadSuccessful);
AZ::EntityId loadedEntityId;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(loadedEntityId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::FindLoadedEntityIdMapping, testEntity->GetId());
// Verify that entityId is not present in the loadedEntityIdMap when remapping is done.
EXPECT_FALSE(loadedEntityId.IsValid());
}
TEST_F(SliceEntityOwnershipTests, OnAssetReady_RootSliceAssetReady_DoNotInstantiate)
{
m_sliceEntityOwnershipService->OnAssetReady(GetRootSliceAsset());
// Verify that validate entities callback is not triggered,
// which will only happen when an attempt to instantiate slice didn't occur.
EXPECT_FALSE(m_validateEntitiesCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, OnAssetError_RootSliceAssetError_DoNotClearOtherSliceInstantiations)
{
AddSlice(AzFramework::EntityList{}, true);
m_sliceEntityOwnershipService->OnAssetError(GetRootSliceAsset());
// Try to finish any queued slice instantiations
AZ::TickBus::ExecuteQueuedEvents();
// Verify that slice instantiation was successful.
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 1);
}
TEST_F(SliceEntityOwnershipTests, OnAssetError_InstantiatingAssetError_StopSliceInstantiation)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset1;
AZ::Data::AssetId sliceAsset1Id = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
sliceAsset1.Create(sliceAsset1Id, false);
AddSlice(AzFramework::EntityList{}, true, sliceAsset1);
AZ::Data::Asset<AZ::SliceAsset> sliceAsset2;
sliceAsset2.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddSlice(AzFramework::EntityList{}, true, sliceAsset2);
m_sliceEntityOwnershipService->OnAssetError(sliceAsset2);
// Try to finish any queued slice instantiations
AZ::TickBus::ExecuteQueuedEvents();
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
// Verify that there is only one slice under root slice
EXPECT_EQ(slicesUnderRootSlice.size(), 1);
// Verify that the slice without the asset error was instantiated
EXPECT_EQ(slicesUnderRootSlice.front().GetSliceAsset().GetId(), sliceAsset1Id);
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_InvalidAssetId_ReturnBlankInstantiationTicket)
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
sliceComponent->AddEntity(aznew AZ::Entity());
// Set the asset id to null to invalidate it.
AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder = AZ::Data::AssetManager::Instance().
CreateAsset<AZ::SliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateNull()));
sliceAssetHolder.Get()->SetData(sliceEntity, sliceComponent);
AzFramework::SliceInstantiationTicket sliceInstantiationTicket;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(sliceInstantiationTicket,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::InstantiateSlice, sliceAssetHolder, nullptr, nullptr);
AZ::TickBus::ExecuteQueuedEvents();
// Verify that there is no request id or context id associated with the sliceInstantiationTicket
EXPECT_EQ(sliceInstantiationTicket.GetContextId(), AZ::Uuid::CreateNull());
EXPECT_EQ(sliceInstantiationTicket.GetRequestId(), 0);
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_InstantiateTwoSlices_SlicesInstantiated)
{
// Add 2 slices asynchronously
AddSlice(AzFramework::EntityList{}, true);
AddSlice(AzFramework::EntityList{}, true);
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 2);
}
TEST_F(SliceEntityOwnershipTests, CloneSliceInstance_InstantiateSlice_SliceCloned)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddSlice(AzFramework::EntityList{ testEntity });
AZ::SliceComponent::EntityIdSet entityIdsInSlice;
GetRootSliceAsset()->GetComponent()->GetEntityIds(entityIdsInSlice);
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
AZ::SliceComponent::SliceInstanceAddress sourceSliceInstanceAddress = rootSlice->FindSlice(*entityIdsInSlice.begin());
AZ::SliceComponent::EntityIdToEntityIdMap entityIdToEntityIdMap;
AZ::SliceComponent::SliceInstanceAddress clonedSliceInstanceAddress;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(clonedSliceInstanceAddress,
&AzFramework::SliceEntityOwnershipServiceRequests::CloneSliceInstance, sourceSliceInstanceAddress, entityIdToEntityIdMap);
// Verify that the entity was cloned successfully with the slice
EXPECT_EQ(clonedSliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities.front()->GetName(), "testEntity");
// Verify that the source slice and the cloned slice have the same reference.
EXPECT_EQ(sourceSliceInstanceAddress.GetReference(), clonedSliceInstanceAddress.GetReference());
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_EntitiesInvalid_SliceInstantiationFailed)
{
m_areEntitiesValidForContext = false;
AddSlice(AzFramework::EntityList{});
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// If entities are invalid, then slice instantiation would fail
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, CancelSliceInstantiation_SetupCorrect_SliceInstantiationCanceled)
{
AzFramework::SliceInstantiationTicket sliceInstantiationTicket = AddSlice(AzFramework::EntityList{}, true);
AzFramework::SliceEntityOwnershipServiceRequestBus::Broadcast(
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::CancelSliceInstantiation, sliceInstantiationTicket);
// This will try to finish any queued slice instantiations.
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, GetOwningSlice_SliceAdded_OwningSliceFetchedCorrectly)
{
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
AddSlice(AzFramework::EntityList{ aznew AZ::Entity() });
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
ASSERT_EQ(sliceReference.GetInstances().size(), 1);
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddress, entitiesOfSlice.front()->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Verify that the source slice and the cloned slice have the same slice asset.
EXPECT_EQ(sliceInstanceAddress.GetReference()->GetSliceAsset(), sliceReference.GetSliceAsset());
}
TEST_F(SliceEntityOwnershipTests, GetOwningSlice_LooseEntityAdded_EntityHasNoOwningSlice)
{
AZ::Entity* testEntity = aznew AZ::Entity();
m_sliceEntityOwnershipService->AddEntity(testEntity);
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddress, testEntity->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Verify that the loose entity doesn't belong to a slice instance
EXPECT_FALSE(sliceInstanceAddress.IsValid());
}
TEST_F(SliceEntityOwnershipDeathTests, AddEntity_RootSliceAssetAbsent_EntityNotCreated)
{
m_sliceEntityOwnershipService->Destroy();
AZ::Entity testEntity = AZ::Entity("testEntity");
EXPECT_DEATH(
{
m_sliceEntityOwnershipService->AddEntity(&testEntity);
}, ".*");
}
}