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,485 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, CreateSlice_ValidSingleParentEntityWithValidChildEntity_EntityStateRemainsTheSame_FT)
{
// Generate Parent entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
// Generate Child entity and set its parent to Parent entity
ASSERT_TRUE(CreateEditorEntity("Child", liveEntityIds, parent).IsValid());
// Capture initial hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidGrandparentParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Build Grandparent->Parent->Child and link parent entities between them
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId grandparent = CreateEditorEntity("Grandparent", liveEntityIds);
ASSERT_TRUE(grandparent.IsValid());
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds, grandparent);
ASSERT_TRUE(parent.IsValid());
ASSERT_TRUE(CreateEditorEntity("Child", liveEntityIds, parent).IsValid());
// Capture initial hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_10DeepParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Build a 10 entity deep hierarchy
AZ::EntityId parent;
for (size_t entityCounter = 0; entityCounter < 10; ++entityCounter)
{
// For each iteration capture the entity made to be used as the parent for the next
parent = CreateEditorEntity(AZStd::string::format("Entity Level %zu", entityCounter).c_str(), liveEntityIds, parent);
ASSERT_TRUE(parent.IsValid());
}
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidParentWith10ValidChildren_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Create the parent entity and hold on to its id
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
// Build 10 children and set all of their parent ids to the same parent entity
for (size_t childEntityCounter = 0; childEntityCounter < 10; ++childEntityCounter)
{
ASSERT_TRUE(CreateEditorEntity(AZStd::string::format("Child #%zu", childEntityCounter + 1).c_str(), liveEntityIds, parent).IsValid());
}
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidParentEntityWithValidChildEntity_OnlyChildEntityAddedToSlice_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Build parent and child entities and connect child to parent
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
AZ::EntityId child = CreateEditorEntity("Child", liveEntityIds, parent);
ASSERT_TRUE(child.IsValid());
// Capture just the child to compare to
EXPECT_TRUE(m_validator.Capture({ child }));
// Build a slice from only the child entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", { child }, sliceInstanceAddress).IsValid());
// Validate that the slice instance only contains the child entity
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_EntityWithExternalReference_ExternalReferenceEntityAutoAddedToSlice_EntityStateRemainsTheSame_FT)
{
// Generate a root entity that will be referenced externally by the entities used to create the slice
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId externalRootId = CreateEditorEntity("ExternalRoot", liveEntityIds);
ASSERT_TRUE(externalRootId.IsValid());
// Generate the entity that will contain the external entity reference to ExternalRoot and set its parent to ExternalRoot
AZ::EntityId entityWithExternalReferenceId = CreateEditorEntity("EntityWithExternalReference", liveEntityIds, externalRootId);
ASSERT_TRUE(entityWithExternalReferenceId.IsValid());
// Acquire the Entity* of EntityWithExternalReference and validate that we successfully acquired it
AZ::Entity* entityWithExternalReference = FindEntityInEditor(entityWithExternalReferenceId);
ASSERT_TRUE(entityWithExternalReference);
// Deactivate the entity so that we can give it a new component
entityWithExternalReference->Deactivate();
// Add an EntityReferenceComponent to EntityWithExternalReference and validate that the component was successfully created
EntityReferenceComponent* externalEntityReferenceComponent = entityWithExternalReference->CreateComponent<EntityReferenceComponent>();
ASSERT_TRUE(externalEntityReferenceComponent);
// Activate the entity
entityWithExternalReference->Activate();
// Set its external entity reference field to the ExternalRoot
externalEntityReferenceComponent->m_entityReference = externalRootId;
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice just from the entity containing the external reference
// Create slice should detect the external reference and auto add ExternalRoot to the slice
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("Slice1", { entityWithExternalReferenceId }, sliceInstanceAddress).IsValid());
// Validate that the slice instance contains both entities
// Confirming that the externally referenced entity was auto added
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_2ValidWithSharedParent_ParentNotIncludedInSliceCreate_ParentIsGenerated_EntityStateRemainsTheSame_FT)
{
// Create a shared parent that won't be included in the CreateSlice call
// Including a shared parent will validate that the generated parent becomes a child of the original parent
// A generated parent is made because CreateSlice will not have a parent entity to work with and one is required
AzToolsFramework::EntityIdList rootParentEntityId;
AZ::EntityId rootParentEntity = CreateEditorEntity("RootParentEntity", rootParentEntityId);
ASSERT_TRUE(rootParentEntity.IsValid());
// Create two entities and set their parent to rootParentEntity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId entity1Id = CreateEditorEntity("Entity1", liveEntityIds, rootParentEntity);
ASSERT_TRUE(entity1Id.IsValid());
AZ::EntityId entity2Id = CreateEditorEntity("Entity2", liveEntityIds, rootParentEntity);
ASSERT_TRUE(entity2Id.IsValid());
// Gather the transform data of entity 1 and entity 2
// Also set the transform data of entity 1 to be different from entity 2
// Since we're calling multiple TransformBus events on entity 1
// We can batch them in an Event lambda
AZ::Transform entity1WorldTransform;
AZ::TransformBus::Event(entity1Id, [&entity1WorldTransform]
(AZ::TransformInterface* transformInterface)
{
AZ::Vector3 entity1LocalTranslate = transformInterface->GetLocalTranslation();
AZ::Vector3 entity1LocalRotation = transformInterface->GetLocalRotation();
transformInterface->SetLocalTranslation(entity1LocalTranslate * 2);
transformInterface->SetLocalRotation(entity1LocalRotation * 2);
entity1WorldTransform = transformInterface->GetWorldTM();
});
AZ::Transform entity2WorldTransform;
AZ::TransformBus::EventResult(entity2WorldTransform, entity2Id, &AZ::TransformBus::Events::GetWorldTM);
// Validate that both transforms are different from the identity
// Validate that both transforms are different from each other
EXPECT_FALSE(entity1WorldTransform.IsClose(AZ::Transform::Identity()));
EXPECT_FALSE(entity2WorldTransform.IsClose(AZ::Transform::Identity()));
EXPECT_FALSE(entity1WorldTransform.IsClose(entity2WorldTransform));
// Create a slice from these two entities
// Create slice should detect that the provided entity list does not contain a shared parent and will generate one
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Grab the instantiated entities within the generated slice instance
const AZ::SliceComponent::InstantiatedContainer* sliceInstanceEntities = sliceInstanceAddress.GetInstance()->GetInstantiated();
ASSERT_TRUE(sliceInstanceEntities);
// Confirm that it contains 3 entities (Entity1, Entity2, GeneratedRoot)
EXPECT_EQ(sliceInstanceEntities->m_entities.size(), 3);
// Validate that the entities are not null
ASSERT_TRUE(sliceInstanceEntities->m_entities[0]);
ASSERT_TRUE(sliceInstanceEntities->m_entities[1]);
ASSERT_TRUE(sliceInstanceEntities->m_entities[2]);
// Validate that the first two entities have the same ids as Entity1 and Entity2
EXPECT_EQ(sliceInstanceEntities->m_entities[0]->GetId(), entity1Id);
EXPECT_EQ(sliceInstanceEntities->m_entities[1]->GetId(), entity2Id);
// Get Entity1's parent id
AZ::EntityId entity1Parent;
AZ::TransformBus::EventResult(entity1Parent, entity1Id, &AZ::TransformBus::Events::GetParentId);
// Get Entity2's parent id
AZ::EntityId entity2Parent;
AZ::TransformBus::EventResult(entity2Parent, entity2Id, &AZ::TransformBus::Events::GetParentId);
// Confirm the parent id is valid and the same between Entity1 and Entity2
EXPECT_TRUE(entity1Parent.IsValid());
EXPECT_EQ(entity1Parent, entity2Parent);
// Confirm that the parentId is not the original parent but instead a new parent
EXPECT_NE(entity1Parent, rootParentEntity);
// Get the parent of entity 1 and 2's parent
// This should be the original rootParentEntity
AZ::EntityId grandparent;
AZ::TransformBus::EventResult(grandparent, entity1Parent, &AZ::TransformBus::Events::GetParentId);
// Confirm that the new parent is a child of the original parent
EXPECT_EQ(grandparent, rootParentEntity);
// Gather the transform information of entity 1 and entity 2 after the create slice operation
AZ::Transform entity1SliceWorldTransform;
AZ::TransformBus::EventResult(entity1SliceWorldTransform, entity1Id, &AZ::TransformBus::Events::GetWorldTM);
AZ::Transform entity2SliceWorldTransform;
AZ::TransformBus::EventResult(entity2SliceWorldTransform, entity2Id, &AZ::TransformBus::Events::GetWorldTM);
// Validate that the create slice operation did not impact the transform data
EXPECT_TRUE(entity1WorldTransform.IsClose(entity1SliceWorldTransform));
EXPECT_TRUE(entity2WorldTransform.IsClose(entity2SliceWorldTransform));
}
TEST_F(SliceStabilityTest, CreateSlice_TestSubsliceOfSameType_EntityStateRemainsTheSame_FT)
{
// Create a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// Capture entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from root entity
AZ::SliceComponent::SliceInstanceAddress parentSliceInstance;
AZ::Data::AssetId parentSliceId = CreateSlice("InheritedSlice", liveEntityIds, parentSliceInstance);
ASSERT_TRUE(parentSliceId.IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(parentSliceInstance));
m_validator.Reset();
// Create a second instance of the slice and make it a child of the Root entity
AZ::SliceComponent::SliceInstanceAddress childSliceInstance = InstantiateEditorSlice(parentSliceId, liveEntityIds, rootEntity);
ASSERT_TRUE(childSliceInstance.IsValid());
// Capture this new hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from this new hierarchy
AZ::SliceComponent::SliceInstanceAddress finalSliceInstance;
AZ::Data::AssetId finalSliceId = CreateSlice("FinalSlice", liveEntityIds, finalSliceInstance);
ASSERT_TRUE(finalSliceId.IsValid());
// Compare genarated slice instance to capture state
EXPECT_TRUE(m_validator.Compare(finalSliceInstance));
}
TEST_F(SliceStabilityTest, CreateSlice_TestSubsliceOfDifferentType_EntityStateRemainsTheSame_FT)
{
// Create a root entity to be used in Slice1
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("Slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
// Capture the entity state of Slice1Root
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create a slice from Slice1Root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
ASSERT_TRUE(CreateSlice("Slice1", slice1Entities, slice1Instance).IsValid());
// Compare generated slice1Instance to Slice1Root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create a root entity to be used in Slice2
AzToolsFramework::EntityIdList slice2Entities;
AZ::EntityId slice2Root = CreateEditorEntity("Slice2Root", slice2Entities);
ASSERT_TRUE(slice2Root.IsValid());
// Capture the entity state of Slice2Root
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create a slice from Slice2Root
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Compare generated slice2Instance to Slice2Root
EXPECT_TRUE(m_validator.Compare(slice2Instance));
m_validator.Reset();
// Make Slice1Root the parent of Slice2Root
AZ::TransformBus::Event(slice2Root, &AZ::TransformBus::Events::SetParent, slice1Root);
// Validate that the parent of Slice2Root was correctly set
AZ::EntityId slice2RootParent;
AZ::TransformBus::EventResult(slice2RootParent, slice2Root, &AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(slice2RootParent, slice1Root);
// Combine entity lists
AzToolsFramework::EntityIdList slice3Entities = slice1Entities;
slice3Entities.insert(slice3Entities.end(), slice2Entities.begin(), slice2Entities.end());
// Capture final hierarchy state
EXPECT_TRUE(m_validator.Capture(slice3Entities));
// Create a slice from final hiararchy
AZ::SliceComponent::SliceInstanceAddress slice3Instance;
ASSERT_TRUE(CreateSlice("Slice3", slice3Entities, slice3Instance).IsValid());
// Compare generated slice instance to capture state
EXPECT_TRUE(m_validator.Compare(slice3Instance));
}
TEST_F(SliceStabilityTest, CreateSlice_Test10DeepSliceAncestry_EntityStateRemainsTheSame_InstanceAncestryIntact_FT)
{
AZ::u32 totalAncestors = 10;
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// Capture the entity state of Root
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
for (AZ::u32 ancestorCount = 0; ancestorCount < totalAncestors; ++ancestorCount)
{
// Continue to make a slice off of Root entity where each iteration Root entity is owned by an instance of the previously made slice
// For each iteration validate the state of each instance matches the state of the initally captured Root entity state
ASSERT_TRUE(CreateSlice(AZStd::string::format("Slice Level: %i", ancestorCount + 1).c_str(), liveEntityIds, sliceInstanceAddress).IsValid());
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
// Acquire the ancestor hierarchy of Root entity
// We pass in totalAncestors + 1 for maxLevels to ensure we rule out the ancestry is greater than expected fail state
AZ::SliceComponent::EntityAncestorList ancestors;
sliceInstanceAddress.GetReference()->GetInstanceEntityAncestry(rootEntity, ancestors, totalAncestors + 1);
// Confirm that the ancestor hierarchy size is the same as the number of slices we iteratively built off of Root entity
EXPECT_EQ(ancestors.size(), totalAncestors);
}
TEST_F(SliceStabilityTest, CreateSlice_Test5DeepSliceAncestryWithSubslices_EntityStateRemainsTheSame_FT)
{
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// This loop moves each iteration's hierarchy into a slice instance
// It then instantiates a second instance and places the second instance under the original hierachy
// This results in the number of entities growing at a power of 2
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
for (size_t ancestorCount = 0; ancestorCount < 5; ++ancestorCount)
{
// Each iteration capture the entity hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the current hierarchy
AZ::Data::AssetId newSlice = CreateSlice(AZStd::string::format("Slice Level: %zu", ancestorCount + 1).c_str(), liveEntityIds, sliceInstanceAddress);
ASSERT_TRUE(newSlice.IsValid());
// Compare the generated slice instance against the capture state and reset the capture for the next iteration
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Instantiate a second copy of this iteration's slice and set Root entity as its parent
// liveEntityIds is updated by this call to include the new instances entities
ASSERT_TRUE(InstantiateEditorSlice(newSlice, liveEntityIds, rootEntity).IsValid());
}
}
TEST_F(SliceStabilityTest, CreateSlice_TestOverride_OverrideAppliesSuccesfully_FT)
{
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
ASSERT_TRUE(CreateEditorEntity("Root", liveEntityIds).IsValid());
// Capture the Root entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from Root entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("Slice1", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance to the capture state and then reset capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Validator passing guarantees instance and its instantiated container are not nullptr and instantiated is size 1
const AZ::SliceComponent::InstantiatedContainer* instantiatedEntities = sliceInstanceAddress.GetInstance()->GetInstantiated();
// Confirm that the first entity entry is not nullptr
EXPECT_TRUE(instantiatedEntities->m_entities[0]);
// Rename the Root entity
constexpr const char* newRootName = "Renamed Root";
instantiatedEntities->m_entities[0]->SetName(newRootName);
// Capture the new entity state which includes the rename
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the renamed Root
// This should create a slice with an override on Slice1 that performs the entity rename
AZ::SliceComponent::SliceInstanceAddress slice2InstanceAddress;
AZ::Data::AssetId slice2Asset = CreateSlice("Slice2", liveEntityIds, slice2InstanceAddress);
ASSERT_TRUE(slice2Asset.IsValid());
// Compare the generated slice instance to the captured entity state
EXPECT_TRUE(m_validator.Compare(slice2InstanceAddress));
// Instantiate a second instance of this slice
// We want to validate that further instantiations after the slice create persist the override
AzToolsFramework::EntityIdList slice2NewInstanceEntities;
AZ::SliceComponent::SliceInstanceAddress slice2NewInstanceAddress = InstantiateEditorSlice(slice2Asset, slice2NewInstanceEntities);
// Confirm the instance is valid
ASSERT_TRUE(slice2NewInstanceAddress.IsValid());
// Acquire the instantiated container from the instance and confirm the container is valid
const AZ::SliceComponent::SliceInstance* slice2NewInstance = slice2NewInstanceAddress.GetInstance();
const AZ::SliceComponent::InstantiatedContainer* newSlice2InstantiatedEntities = slice2NewInstance->GetInstantiated();
ASSERT_TRUE(newSlice2InstantiatedEntities);
// Confirm that the slice instance contains only 1 entity and that its name matches the renamed entity
ASSERT_EQ(newSlice2InstantiatedEntities->m_entities.size(), 1);
ASSERT_TRUE(newSlice2InstantiatedEntities->m_entities[0]);
EXPECT_EQ(newSlice2InstantiatedEntities->m_entities[0]->GetName(), newRootName);
}
}
@@ -0,0 +1,303 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, PushToSlice_PushSingleEntityToSlice_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used in a slice
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create an entity to be pushed to slice and set its parent to be the first SliceEntity
AZ::EntityId addedEntity = CreateEditorEntity("AddedEntity", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push AddedEntity to the existing slice instance
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_PushSingleParentEntityWithChildEntity_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create a parent and child entity to be pushed to the slice
// Set AddedParent's parent to be SliceEntity
// Set AddedChild's parent to be AddedParent
AZ::EntityId addedParent = CreateEditorEntity("AddedParent", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedParent.IsValid());
AZ::EntityId addedChild = CreateEditorEntity("AddedChild", liveEntityIds, addedParent);
ASSERT_TRUE(addedChild.IsValid());
// Capture the current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push AddedParent and AddedChild to the existing slice instance
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
// Disabled in SPEC-3077
TEST_F(SliceStabilityTest, DISABLED_PushToSlice_PushGrandparentParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create a grandparent->parent->child to be pushed to the slice and connect their parent hierarchy accordingly
AZ::EntityId addedGrandparent = CreateEditorEntity("AddedGrandParent", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedGrandparent.IsValid());
AZ::EntityId addedParent = CreateEditorEntity("AddedParent", liveEntityIds, addedGrandparent);
ASSERT_TRUE(addedParent.IsValid());
AZ::EntityId addedChild = CreateEditorEntity("AddedChild", liveEntityIds, addedParent);
ASSERT_TRUE(addedChild.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push grandparent, parent, and child to slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_Push10DeepParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Generate 10 new entities and set each entity's parent to be the entity generated before them
// This creates a 10 child deep hierarchy that we will push to slice
AZ::EntityId parent = sliceEntity;
for (size_t entityCounter = 0; entityCounter < 10; ++entityCounter)
{
parent = CreateEditorEntity(AZStd::string::format("Added Entity Level %zu", entityCounter).c_str(), liveEntityIds, parent);
ASSERT_TRUE(parent.IsValid());
}
// Capture the current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push the newly created entities into the existing slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_Push10Children_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
AZ::EntityId addedEntity;
AzToolsFramework::EntityIdList entitiesToPush;
for (size_t childEntityCounter = 0; childEntityCounter < 10; ++childEntityCounter)
{
// Generate a set of children who share the same parent (SliceEntity) and add them to the list of entities to push
addedEntity = CreateEditorEntity(AZStd::string::format("Child #%zu", childEntityCounter).c_str(), liveEntityIds, sliceEntity);
ASSERT_TRUE(addedEntity.IsValid());
entitiesToPush.emplace_back(addedEntity);
}
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push the created child entities to the existing slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_PushNestedSliceOfDifferentType_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used for Slice1's root
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
// Capture entity state for slice1Root
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create a slice from slice1Root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
ASSERT_TRUE(CreateSlice("Slice1", slice1Entities, slice1Instance).IsValid());
// Compare the state of slice1Instance to the captured state of slice1Root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create an entity to be used for Slice2's root and make its parent slice1Root
AzToolsFramework::EntityIdList slice2Entities;
AZ::EntityId slice2Root = CreateEditorEntity("Slice2Root", slice2Entities);
ASSERT_TRUE(slice2Root.IsValid());
// Provide Slice2Root a child entity to confirm all entities in Slice2 are included in the push
ASSERT_TRUE(CreateEditorEntity("Slice2Child", slice2Entities, slice2Root).IsValid());
// Capture entity state for Slice2Root
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create a slice from slice2Root
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Compare the state of slice2Instance to the captured state of slice2Root
EXPECT_TRUE(m_validator.Compare(slice2Instance));
m_validator.Reset();
// Parent slice2Root under slice1Root to prepare for the push
ReparentEntity(slice2Root, slice1Root);
// Combine the current entity lists
AzToolsFramework::EntityIdList totalEntities = slice1Entities;
totalEntities.insert(totalEntities.end(), slice2Entities.begin(), slice2Entities.end());
// Capture the total entity hierarchy state
EXPECT_TRUE(m_validator.Capture(totalEntities));
// Push the slice2Root entity into slice1Instance
ASSERT_TRUE(PushEntitiesToSlice(slice1Instance, totalEntities));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(slice1Instance));
}
TEST_F(SliceStabilityTest, PushToSliceAndCreateSlice_ValidateCombinationOfPushCreateOperations_EntityStateRemainsTheSame_FT)
{
// Create Slice1 root
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("Slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create Slice1 from Slice1 root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", slice1Entities, slice1Instance);
ASSERT_TRUE(slice1Asset.IsValid());
// Validate that Slice1 instance did not change the structure of Slice1 root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create Slice1 child and make Slice1 root its parent
AZ::EntityId slice1Child = CreateEditorEntity("Slice1Child", slice1Entities, slice1Root);
ASSERT_TRUE(slice1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Push Slice1 child to Slice1
ASSERT_TRUE(PushEntitiesToSlice(slice1Instance, slice1Entities));
// Validate that Slice1 root and child did not change during push
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Instantiate a second instance of Slice1 and make the original Slice 1 child its parent
AzToolsFramework::EntityIdList secondSlice1InstanceEntities;
ASSERT_TRUE(InstantiateEditorSlice(slice1Asset, secondSlice1InstanceEntities, slice1Child).IsValid());
// Slice 2 entities will be the combination of both Slice1 instances
AzToolsFramework::EntityIdList slice2Entities = slice1Entities;
slice2Entities.insert(slice2Entities.end(), secondSlice1InstanceEntities.begin(), secondSlice1InstanceEntities.end());
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create slice 2
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Validate that entities in the Slice 2 instance are structurally the same as the input entities in its creation
EXPECT_TRUE(m_validator.Compare(slice2Instance));
}
}
@@ -0,0 +1,92 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, ReParent_SliceEntityMovedFromOneInstanceToAnother_EntityIDRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList instance1Entities;
AZ::EntityId instance1Root = CreateEditorEntity("Slice1Root", instance1Entities);
ASSERT_TRUE(instance1Root.IsValid());
AZ::EntityId instance1Child = CreateEditorEntity("Slice1Child", instance1Entities, instance1Root);
ASSERT_TRUE(instance1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(instance1Entities));
AZ::SliceComponent::SliceInstanceAddress slice1InstanceAddress;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", instance1Entities, slice1InstanceAddress);
ASSERT_TRUE(slice1Asset.IsValid());
EXPECT_TRUE(m_validator.Compare(slice1InstanceAddress));
AzToolsFramework::EntityIdList instance2Entities;
AZ::EntityId instance2Root = CreateEditorEntity("Slice2Root", instance2Entities);
ASSERT_TRUE(instance2Root.IsValid());
AZ::SliceComponent::SliceInstanceAddress slice2InstanceAddress;
ASSERT_TRUE(CreateSlice("Slice2", instance2Entities, slice2InstanceAddress).IsValid());
ReparentEntity(instance1Child, instance2Root);
AzToolsFramework::EntityIdList instance2RootChildren;
AZ::TransformBus::EventResult(instance2RootChildren, instance2Root, &AZ::TransformBus::Events::GetChildren);
ASSERT_EQ(instance2RootChildren.size(), 1);
ASSERT_EQ(instance2RootChildren[0], instance1Child);
}
/*
Even though we are not explicitly reparenting here,creating a nested slice from a slice instance reparents the slice internally.
Therefore, this test belongs in this class.
*/
TEST_F(SliceStabilityTest, ReParent_NestedSliceCreatedFromSliceInstanceChild_SliceHierarchyRemainsSame_FT)
{
AzToolsFramework::EntityIdList instance1Entities;
AZ::EntityId instance1Root = CreateEditorEntity("Slice1Root", instance1Entities);
ASSERT_TRUE(instance1Root.IsValid());
AZ::EntityId instance1Child = CreateEditorEntity("Slice1Child", instance1Entities, instance1Root);
ASSERT_TRUE(instance1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(instance1Entities));
AZ::SliceComponent::SliceInstanceAddress slice1InstanceAddress;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", instance1Entities, slice1InstanceAddress);
ASSERT_TRUE(slice1Asset.IsValid());
EXPECT_TRUE(m_validator.Compare(slice1InstanceAddress));
m_validator.Reset();
AzToolsFramework::EntityIdList nestedSliceEntities = AzToolsFramework::EntityIdList{ instance1Child };
EXPECT_TRUE(m_validator.Capture(nestedSliceEntities));
AZ::SliceComponent::SliceInstanceAddress nestedSliceInstanceAddress;
AZ::Data::AssetId nestedSliceAsset =
CreateSlice("NestedSlice", nestedSliceEntities, nestedSliceInstanceAddress);
ASSERT_TRUE(nestedSliceAsset.IsValid());
EXPECT_TRUE(m_validator.Compare(nestedSliceInstanceAddress));
const AZ::SliceComponent::EntityList nestedSliceInstanceEntities =
nestedSliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities;
EXPECT_EQ(nestedSliceInstanceEntities.size(), 1);
AZ::EntityId nestedSliceRootParentId;
AZ::TransformBus::EventResult(nestedSliceRootParentId, nestedSliceInstanceEntities[0]->GetId(), &AZ::TransformBus::Events::GetParentId);
// Validate that the parent of nested slice root is the same as the parent of the instance it was created from.
EXPECT_EQ(instance1Root, nestedSliceRootParentId);
}
}
@@ -0,0 +1,717 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Slice/SliceAsset.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
namespace UnitTest
{
void SliceStabilityTest::SetUpEditorFixtureImpl()
{
auto* app = GetApplication();
ASSERT_TRUE(app);
// Get the serialize context to reflect our types and set our validator's serialize context
AZ::SerializeContext* serializeContext = app->GetSerializeContext();
m_validator.SetSerializeContext(serializeContext);
app->RegisterComponentDescriptor(EntityReferenceComponent::CreateDescriptor());
// Grab the system entity from the component application
AZ::Entity* systemEntity = app->FindEntity(AZ::SystemEntityId);
// Deactivate the AssetSystemComponent
// We will be implementing the AssetSystemRequestBus and want to avoid Ebus connection conflicts
AzToolsFramework::AssetSystem::AssetSystemComponent* assetSystemComponent = systemEntity->FindComponent<AzToolsFramework::AssetSystem::AssetSystemComponent>();
assetSystemComponent->Deactivate();
AzToolsFramework::AssetSystemRequestBus::Handler::BusConnect();
AzToolsFramework::EditorRequestBus::Handler::BusConnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
// Cache the existing file io instance and build our mock file io
m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
m_fileIOMock = AZStd::make_unique<testing::NiceMock<AZ::IO::MockFileIOBase>>();
// Swap out current file io instance for our mock
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_fileIOMock.get());
// Setup the default returns for our mock file io calls
AZ::IO::MockFileIOBase::InstallDefaultReturns(*m_fileIOMock.get());
// For write we set the default of the 4th param (bytesWritten) to 1
// otherwise slice transaction errors out during the mock write for writing the default 0 bytes
ON_CALL(*m_fileIOMock.get(), Write(testing::_, testing::_, testing::_, testing::_))
.WillByDefault(
testing::DoAll(
testing::SetArgPointee<3>(1),
testing::Return(AZ::IO::Result(AZ::IO::ResultCode::Success))));
ON_CALL(*m_fileIOMock.get(), GetAlias(testing::_))
.WillByDefault(
testing::Return(""));
ON_CALL(*m_fileIOMock.get(), Rename(testing::_, testing::_))
.WillByDefault(
testing::Return(AZ::IO::Result(AZ::IO::ResultCode::Success)));
}
void SliceStabilityTest::TearDownEditorFixtureImpl()
{
// Get the system entity from the component application
AZ::Entity* systemEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(systemEntity, &AZ::ComponentApplicationBus::Events::FindEntity, AZ::SystemEntityId);
// Deactivate the EditorEntityContextComponent
// This triggers the entity context to destroy its root slice asset which destroys all entities, slice instances, and meta data entities
AzToolsFramework::EditorEntityContextComponent* editorEntityContext = systemEntity->FindComponent<AzToolsFramework::EditorEntityContextComponent>();
editorEntityContext->Deactivate();
// Restore our original file io instance
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
AzToolsFramework::EditorRequestBus::Handler::BusDisconnect();
AzToolsFramework::AssetSystemRequestBus::Handler::BusDisconnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
}
AZ::EntityId SliceStabilityTest::CreateEditorEntity(const char* entityName, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parentId /*= AZ::EntityId()*/)
{
// Start by creating and registering a new loose entity with the editor entity context
// This call also adds required components onto the entity
AZ::EntityId newEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
newEntityId, &AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntity, entityName);
AZ::Entity* newEntity = AzToolsFramework::GetEntityById(newEntityId);
// If newEntity is nullptr still then there was a failure in the above EBus call and we cannot proceed
if (!newEntity)
{
return AZ::EntityId();
}
// Add to our entities container
entityList.emplace_back(newEntity->GetId());
// Get the new entity's transform component
AzToolsFramework::Components::TransformComponent* entityTransform =
newEntity->FindComponent<AzToolsFramework::Components::TransformComponent>();
// If new entity has no Transform component then there was a failure in the create entity call
// and the application of required components
if (!entityTransform)
{
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity, newEntity->GetId());
return AZ::EntityId();
}
// If supplied set the parent of the new entity
if (parentId.IsValid())
{
entityTransform->SetParent(parentId);
}
// Set the new entity's transform to non zero values
// This helps validate in comparison tests that the transform values of created entities persist during slice operations
entityTransform->SetLocalScale(AZ::Vector3(5, 5, 5));
entityTransform->SetLocalRotation(AZ::Vector3RadToDeg(AZ::Vector3(90, 90, 90)));
entityTransform->SetLocalTranslation(AZ::Vector3(100, 100, 100));
return entityList.back();
}
AZ::Data::AssetId SliceStabilityTest::CreateSlice(AZStd::string sliceAssetName, AzToolsFramework::EntityIdList entityList, AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
// Fabricate a new asset id for this slice and set its sub id to the SliceAsset sub id
m_newSliceId = AZ::Uuid::CreateRandom();
m_newSliceId.m_subId = AZ::SliceAsset::GetAssetSubId();
// Init the sliceAddress to invalid
sliceAddress = AZ::SliceComponent::SliceInstanceAddress();
// The relative slice asset path will be used in registering the slice with the asset catalog
// It will show up in debugging and is useful for tracking multiple slice assets in a test
// Since we are mocking file io m_relativeSourceAssetRoot is purely cosmetic
AZStd::string relativeSliceAssetPath = m_relativeSourceAssetRoot + sliceAssetName;
// Call MakeNewSlice and deactivate all prompts for user input
// Since MakeNewSlice is tightly joined to QT dialogs and popups we default all decisions and silence all popups so we can run tests without user input
// inheritSlices: whether to inherit slice ancestry of added instance entities or make a new slice with no ancestry
// setAsDynamic: whether to mark the slice asset as dynamic
// acceptDefaultPath: whether to prompt the user for a path save location or to proceed with the generated one
// defaultMoveExternalRefs: whether to prompt the user on if external entity references found in added entities get added to the created slice or do this automatically
// defaultGenerateSharedRoot: whether to generate a shared root if one or more added entities do not share the same root
// silenceWarningPopups: disables QT warning popups from being generated, we can still rely on the return of MakeNewSlice for error handling
bool sliceCreateSuccess = AzToolsFramework::SliceUtilities::MakeNewSlice(AzToolsFramework::EntityIdSet(entityList.begin(), entityList.end()),
relativeSliceAssetPath.c_str(),
true /*inheritSlices*/,
false /*setAsDynamic*/,
true /*acceptDefaultPath*/,
true /*defaultMoveExternalRefs*/,
true /*defaultGenerateSharedRoot*/,
true /*silenceWarningPopups*/);
if (sliceCreateSuccess)
{
// Setup the mock asset info for our new slice
AZ::Data::AssetInfo newSliceInfo;
newSliceInfo.m_assetId = m_newSliceId;
newSliceInfo.m_relativePath = relativeSliceAssetPath;
newSliceInfo.m_assetType = azrtti_typeid<AZ::SliceAsset>();
newSliceInfo.m_sizeBytes = 1;
// Register the asset with the asset catalog
// This mocks the asset load pipeline that triggers the OnCatalogAssetAdded event
// OnCatalogAssetAdded triggers the final steps of the create slice flow by building the first slice instance out of the added entities
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, m_newSliceId, newSliceInfo);
}
else
{
return AZ::Uuid::CreateNull();
}
// Acquire the slice instance address the added entities were promoted into
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, *entityList.begin(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Validate the slice instance
if (!sliceAddress.IsValid())
{
return AZ::Uuid::CreateNull();
}
// Validate the new slice asset id matches our generated asset id
AZ::Data::AssetId createdSliceId = sliceAddress.GetReference()->GetSliceAsset().GetId();
if (m_newSliceId != createdSliceId)
{
// Return invalid id as error
createdSliceId = AZ::Uuid::CreateNull();
}
// Reset our newSliceId so it's invalid for any OnSliceInstantiated calls
m_newSliceId = AZ::Uuid::CreateNull();
return createdSliceId;
}
bool SliceStabilityTest::PushEntitiesToSlice(AZ::SliceComponent::SliceInstanceAddress& sliceInstanceAddress, const AzToolsFramework::EntityIdList& entitiesToPush)
{
// Nothing to push
if (entitiesToPush.empty())
{
return true;
}
// Cannot push to an invalid slice
if (!sliceInstanceAddress.IsValid())
{
return false;
}
// Copy the slice instance id
// The internal instance of the slicecomponent we push to will be destroyed
// We will use this id to validate that the new instance maps to the same id after the push
AZ::SliceComponent::SliceInstance* sliceInstance = sliceInstanceAddress.GetInstance();
AZ::SliceComponent::SliceInstanceId sliceInstanceId = sliceInstance->GetId();
// Get the currently instantiated entities in this slice instance
const AZ::SliceComponent::EntityList& sliceInstanceInstantiatedEntities = sliceInstance->GetInstantiated() ? sliceInstance->GetInstantiated()->m_entities : AZ::SliceComponent::EntityList();
// Acquire the slice instance's asset and start the push slice transaction
const AZ::Data::Asset<AZ::SliceAsset> sliceAsset = sliceInstanceAddress.GetReference()->GetSliceAsset();
AzToolsFramework::SliceUtilities::SliceTransaction::TransactionPtr transaction = AzToolsFramework::SliceUtilities::SliceTransaction::BeginSlicePush(sliceAsset);
// Since a slice push causes the current instance to re-instantiate all added entities will be remade in the new instance
// We will be deleting the existing entities being added as they will be replaced in this manner
AzToolsFramework::EntityIdList entitiesToRemove;
for (const AZ::EntityId& entityToPush : entitiesToPush)
{
AzToolsFramework::SliceUtilities::SliceTransaction::Result result;
// If the entity already exists in the slice then we will update it
if (FindEntityInList(entityToPush, sliceInstanceInstantiatedEntities))
{
result = transaction->UpdateEntity(entityToPush);
}
else
{
// Otherwise we add it to the slice transaction
// and mark the entity for delete since it will be replaced
result = transaction->AddEntity(entityToPush);
entitiesToRemove.emplace_back(entityToPush);
}
if (!result.IsSuccess())
{
return false;
}
}
// This asset mocks the reloaded temp asset that would trigger the ReloadAssetFromData call after a slice push
AZ::Data::Asset<AZ::SliceAsset> slicePushResultClone;
AzToolsFramework::SliceUtilities::SliceTransaction::PostSaveCallback postSaveCallback =
[&sliceAsset, &slicePushResultClone](AzToolsFramework::SliceUtilities::SliceTransaction::TransactionPtr transaction, const char* fullSourcePath, const AzToolsFramework::SliceUtilities::SliceTransaction::SliceAssetPtr& asset) -> void
{
// SlicePostPushCallback updates the slice component that owns our instance's reference (usually the root slice component of the entity context)
// the update is to make a mapping of the existing entity id (about to be deleted) with the asset entity id (about to be instantiated and replace the existing)
// this sets the replacement entity back to its original id so that external references to that entity do not break by it not having the same id
AzToolsFramework::SliceUtilities::SlicePostPushCallback(transaction, fullSourcePath, asset);
// Clone our slice asset so that our temp has the same asset id
slicePushResultClone = { sliceAsset.Get()->Clone(), AZ::Data::AssetLoadBehavior::Default };
// Move the transaction's asset data into our temp
// the transaction's asset data is what would be saved to disk and reloaded into our temp
slicePushResultClone.Get()->SetData(asset.Get()->GetEntity(), asset.Get()->GetComponent());
asset.Get()->SetData(nullptr, nullptr, false);
};
// Commit our queued entity adds and updates to be pushed to our slice asset and set our pre and post commit callbacks
const AzToolsFramework::SliceUtilities::SliceTransaction::Result result = transaction->Commit(
"NotAValidAssetPath",
AzToolsFramework::SliceUtilities::SlicePreSaveCallbackForWorldEntities,
postSaveCallback);
if (!result.IsSuccess())
{
return false;
}
// Send the reload event that will trigger the owning slice component to re-instantiate its data with what was "written" to disk
// This replaces our deleted entities with their versions pushed to the slice and rebuilds our slice instance to contain those entities
// Because of the mapping we did in the post commit callback they will be re-mapped back to their original ids during the instantiation process
AZ::Data::AssetManager::Instance().ReloadAssetFromData(slicePushResultClone);
// Acquire the root slice
AZ::SliceComponent* rootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
if (!rootSlice)
{
return false;
}
// Find the owning slice instance of one of the entities we added
// This instance should contain all entities prior to the push plus the pushed entities
// We need to update the slice instance here since the instantiated entities in the original instance have been destroyed and re-allocated
// The data and ids should be the same but the SliceInstance* and SliceReference* of the input instance address are invalid and need to be updated
sliceInstanceAddress = rootSlice->FindSlice(*entitiesToPush.begin());
// The instance should be valid and its instance id should match our original instance before the asset reload
if (!sliceInstanceAddress.IsValid() ||
(sliceInstanceAddress.GetInstance()->GetId() != sliceInstanceId))
{
return false;
}
return true;
}
AZ::SliceComponent::SliceInstanceAddress SliceStabilityTest::InstantiateEditorSlice(AZ::Data::AssetId sliceAssetId, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parent /*= AZ::EntityId()*/)
{
// Make sure we've created this asset before trying to instantiate it
auto findIt = m_createdSlices.find(sliceAssetId);
if (findIt == m_createdSlices.end())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Cache how many instances of this asset exist currently
size_t currentInstanceCount = findIt->second.size();
// Acquire the SliceAsset
AZ::Data::Asset<AZ::SliceAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::SliceAsset>(sliceAssetId, AZ::Data::AssetLoadBehavior::Default);
if (asset.GetStatus() != AZ::Data::AssetData::AssetStatus::NotLoaded)
{
asset.BlockUntilLoadComplete();
}
if (!asset)
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Instantiate a new slice instance into the editor from the slice asset
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(m_ticket,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::InstantiateEditorSlice,
asset, AZ::Transform::CreateIdentity());
// InstantiateEditorSlice queued the actual instantiation logic onto the tick bus queued events
// Execute the tickbus queue to complete the instantiation
// This should trigger our OnSliceInstantiated callback
AZ::TickBus::ExecuteQueuedEvents();
// Validate that our instances under this asset have grown by 1
// This confirms that OnSliceInstantiated was called during ExecuteQueuedEvents
if (findIt->second.size() != (currentInstanceCount + 1))
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// OnSliceInstantiated has updated the instance list for this asset
// Acquire it now and check if it's valid
AZ::SliceComponent::SliceInstanceAddress& newInstanceAddress = findIt->second.back();
if (!newInstanceAddress.IsValid())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Get the root entity of our new instance and check if it's valid
AZ::EntityId sliceInstanceRoot;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(sliceInstanceRoot, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetRootEntityIdOfSliceInstance, newInstanceAddress);
if (!sliceInstanceRoot.IsValid())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// If a parent was provided then make it the parent of our new slice instance
if (parent.IsValid())
{
AZ::TransformBus::Event(sliceInstanceRoot, &AZ::TransformBus::Events::SetParent, parent);
}
// Reset our ticket
m_ticket = AzFramework::SliceInstantiationTicket();
// For each of the new instances instantiated entities
// Add them to our live entity id list
const AZ::SliceComponent::EntityList& instanceEntities = newInstanceAddress.GetInstance()->GetInstantiated()->m_entities;
for (const AZ::Entity* instanceEntity : instanceEntities)
{
if (instanceEntity)
{
entityList.emplace_back(instanceEntity->GetId());
}
}
// Return the new instance
return newInstanceAddress;
}
void SliceStabilityTest::ReparentEntity(AZ::EntityId& entity, const AZ::EntityId& newParent)
{
if (AzToolsFramework::SliceUtilities::IsReparentNonTrivial(entity, newParent))
{
AzToolsFramework::SliceUtilities::ReparentNonTrivialSliceInstanceHierarchy(entity, newParent);
}
else
{
AZ::TransformBus::Event(entity, &AZ::TransformBus::Events::SetParent, newParent);
}
}
// A helper to find an entity within an entity list
// Used to determine whether to update or push an entity to slice
// As well as to sort our comparison captures in tests
AZ::Entity* SliceStabilityTest::FindEntityInList(const AZ::EntityId& entityId, const AZ::SliceComponent::EntityList& entityList)
{
auto findIt = AZStd::find_if(entityList.begin(), entityList.end(),
[&entityId](AZ::Entity* entity) -> bool
{
if (entity && entity->GetId() == entityId)
{
return true;
}
return false;
});
if (findIt != entityList.end())
{
return *findIt;
}
return nullptr;
}
// Wrapper around finding an entity in the Editor Root Slice
AZ::Entity* SliceStabilityTest::FindEntityInEditor(const AZ::EntityId& entityId)
{
AZ::SliceComponent* editorRootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(editorRootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
if (!editorRootSlice)
{
return nullptr;
}
return editorRootSlice->FindEntity(entityId);
}
/*
* EditorEntityContextNotificationBus
*/
void SliceStabilityTest::OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& ticket)
{
if (!sliceAssetId.IsValid())
{
EXPECT_TRUE(sliceAssetId.IsValid());
return;
}
// We instantiate slices in 2 manners
// The first is creating a new slice asset and in this case we have no ticket to check against so check the asset id
// The other is we instantiated an instance from an existing asset and we have a ticket to compare against
if (ticket == m_ticket || sliceAssetId == m_newSliceId)
{
m_createdSlices[sliceAssetId].emplace_back(sliceAddress);
m_ticket = AzFramework::SliceInstantiationTicket();
}
}
void SliceStabilityTest::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& ticket)
{
// This should never occur for an instantiation we're responsible for
EXPECT_FALSE(ticket == m_ticket || sliceAssetId == m_newSliceId);
}
/*
* EditorRequestBus
*/
void SliceStabilityTest::CreateEditorRepresentation(AZ::Entity* entity)
{
if (!entity)
{
EXPECT_TRUE(entity);
return;
}
// CreateEditorEntity triggers this event so we add required components here
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *entity);
}
/*
* AssetSystemRequestBus
*/
bool SliceStabilityTest::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& watchFolder)
{
// Mock stub for GetSourceInfoBySourcePath
// This call is invoked during Create Slice to predict the asset id of the new slice before it gets processed
assetInfo.m_relativePath = sourcePath;
assetInfo.m_assetId = m_newSliceId;
return true;
}
SliceStabilityTest::SliceOperationValidator::SliceOperationValidator() :
m_serializeContext(nullptr)
{
}
SliceStabilityTest::SliceOperationValidator::~SliceOperationValidator()
{
// Destroy any entities within our capture and clear our capture list
Reset();
}
void SliceStabilityTest::SliceOperationValidator::SetSerializeContext(AZ::SerializeContext* serializeContext)
{
m_serializeContext = serializeContext;
}
bool SliceStabilityTest::SliceOperationValidator::Capture(const AzToolsFramework::EntityIdList& entitiesToCapture)
{
// We either haven't released our current capture or were given nothing to capture or we weren't activated
if (!m_entityStateCapture.empty() || entitiesToCapture.empty() || !m_serializeContext)
{
return false;
}
// Validate that all entities to capture are real entities in the Editor Entity Context
// Place their Entity* in a temp list to clone
AZ::SliceComponent::EntityList captureList;
for (const AZ::EntityId& entityId : entitiesToCapture)
{
AZ::Entity* entity = FindEntityInEditor(entityId);
if (!entity)
{
return false;
}
captureList.emplace_back(entity);
}
// Clone the entities
// The clones should not be active within the entity context and are safe from our slice operations
m_serializeContext->CloneObjectInplace(m_entityStateCapture, &captureList);
// Success if the clone completed and matches the size of the input
return m_entityStateCapture.size() == entitiesToCapture.size();
}
bool SliceStabilityTest::SliceOperationValidator::Compare(const AZ::SliceComponent::SliceInstanceAddress& instanceToCompare)
{
// We've either captured nothing or our instance to compare has no instantiated entities
if (m_entityStateCapture.empty() || !instanceToCompare.IsValid() || !instanceToCompare.GetInstance()->GetInstantiated())
{
return false;
}
// Get the instantiated list of entities and early out if the entity count doesn't match out capture
AZ::SliceComponent::EntityList instanceEntityList = instanceToCompare.GetInstance()->GetInstantiated()->m_entities;
if (instanceEntityList.size() != m_entityStateCapture.size())
{
return false;
}
// Since slice instantiation can alter the order of entities against the original input we need to sort our capture to match
// We do not care if the order of entities is different, only that both sets of entities are identical
// SortCapture will early out if a comparison entity cannot be found in our capture
if (!SortCapture(instanceEntityList))
{
return false;
}
// Build a data patch between our sorted capture and the instantiated comparison entities
// This will diff every reflected element within both entity lists including: Entity Ids, Parent/Child Hierarchies, Component IDs, Component properties, etc.
AZ::DataPatch patch;
bool result = patch.Create(&m_entityStateCapture, &instanceEntityList, AZ::DataPatch::FlagsMap(), AZ::DataPatch::FlagsMap(), m_serializeContext);
// If the patch has any delta between the two then they do not match
return result & !patch.IsData();
}
bool SliceStabilityTest::SliceOperationValidator::SortCapture(const AzToolsFramework::EntityList& orderToMatch)
{
// Since slice instantiation can alter the order of entities against the original input we need to sort our capture to match
// We do not care if the order of entities is different, only that both sets of entities are identical
// SortCapture will early out if a comparison entity cannot be found in our capture
AzToolsFramework::EntityList sortedCapture;
for (const AZ::Entity* entity : orderToMatch)
{
// If an entity is ever nullptr early out
if (!entity)
{
return false;
}
// Try and find the entity within our capture state, early out if we can't find it
AZ::Entity* foundCaptureEntity = FindEntityInList(entity->GetId(), m_entityStateCapture);
if (!foundCaptureEntity)
{
return false;
}
// Place the found entity into our temp
// This builds a sequence of entities that match our orderToMatch list
sortedCapture.emplace_back(foundCaptureEntity);
}
// Update our capture
m_entityStateCapture = sortedCapture;
return true;
}
void SliceStabilityTest::SliceOperationValidator::Reset()
{
// Since our entity capture is made of clones we need to delete them
for (AZ::Entity* capturedEntity : m_entityStateCapture)
{
EXPECT_NE(capturedEntity, nullptr);
delete capturedEntity;
}
m_entityStateCapture.clear();
}
void SliceStabilityTest::EntityReferenceComponent::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<EntityReferenceComponent, AzToolsFramework::Components::EditorComponentBase>()->
Field("EntityReference", &EntityReferenceComponent::m_entityReference);
}
}
// Sanity check test to confirm validator will catch differences
TEST_F(SliceStabilityTest, ValidatorCompare_DifferenceInObjects_DifferenceDetected_FT)
{
// Generate a root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntityId = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntityId.IsValid());
// Capture entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the root entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AZ::Data::AssetId newSliceAssetId = CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress);
ASSERT_TRUE(newSliceAssetId.IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
// Make a second instance of our new slice
// This instance should have a unique entity id for its root entity
AzToolsFramework::EntityIdList newInstanceEntities;
AZ::SliceComponent::SliceInstanceAddress newInstanceAddress = InstantiateEditorSlice(newSliceAssetId, newInstanceEntities);
ASSERT_TRUE(newInstanceAddress.IsValid());
// Validate that our first instance has a single valid entity
ASSERT_TRUE(sliceInstanceAddress.IsValid());
ASSERT_TRUE(sliceInstanceAddress.GetInstance()->GetInstantiated());
ASSERT_EQ(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities.size(), 1);
ASSERT_TRUE(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]);
// Validate that our first instance's entity has rootEntityId as its EntityID
EXPECT_EQ(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId(), rootEntityId);
// Validate that our second instance has a single valid entity
ASSERT_TRUE(newInstanceAddress.IsValid());
ASSERT_TRUE(newInstanceAddress.GetInstance()->GetInstantiated());
ASSERT_EQ(newInstanceAddress.GetInstance()->GetInstantiated()->m_entities.size(), 1);
ASSERT_TRUE(newInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]);
// Validate that our two instances have different EntityIDs for their root entities
EXPECT_NE(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId(), newInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId());
// Compare the new instance against the inital capture
// We expect the compare to fail since there is a difference in entity ids
EXPECT_FALSE(m_validator.Compare(newInstanceAddress));
}
}
@@ -0,0 +1,171 @@
/*
* 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/std/functional.h>
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
class SliceStabilityTest
: public ToolsApplicationFixture,
public AzToolsFramework::AssetSystemRequestBus::Handler,
public AzToolsFramework::EditorRequestBus::Handler,
public AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler
{
public:
//! Creates an entity within the EditorEntityContext and supplies it required components
//! @param entityName The name the created entity will use
//! @param entityList The created entity will be placed at the back of the provided entityList
//! @param parentId The EntityId to be assigned as the parent of the created entity.
//! Defaults to an invalid id
//! @return The EntityId of the created Entity, or an invalid id if operation failed
AZ::EntityId CreateEditorEntity(const char* entityName, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parentId = AZ::EntityId());
//! Creates a new slice asset out of the provided entityList and generates the first slice instance using the provided entityList
//! @param sliceAssetName Name of the newly created slice Asset
//! @param entityList Created slice will be comprised of the entity hierarchy found in entityList
//! entities need to exist within the EditorEntityContext and those same entities will be promoted into the first slice instance of the created slice
//! @param sliceAddress The SliceInstanceAddress of the first slice instance generated out of the entities found in entityList
//! @return The AssetId of the newly created slice, or an invalid id if operation failed
AZ::Data::AssetId CreateSlice(AZStd::string sliceAssetName, AzToolsFramework::EntityIdList entityList, AZ::SliceComponent::SliceInstanceAddress& sliceAddress);
//! Pushes a set of entities to an existing slice asset generated via CreateSlice
//! @param sliceInstanceAddress An instance of the slice being pushed to
//! Note: The act of pushing to a slice destroys and remakes all existing instances sliceInstanceAddress will be updated to represent the remade slice instance.
//! Any copies of sliceInstanceAddress from before this operation are invalid.
//! @param entitiesToPush A list of entities to push to the slice.
//! If an entity in the list is already in the slice instance then it will be pushed as an updated entity
//! If an entity in the list is not in the slice instance then it will be pushed as an added entity
//! @return true if the push succeeded, or false if the operation failed
bool PushEntitiesToSlice(AZ::SliceComponent::SliceInstanceAddress& sliceInstanceAddress, const AzToolsFramework::EntityIdList& entitiesToPush);
//! Instantiates a slice into the EditorEntityContext using an existing slice Asset created via CreateSlice
//! @param sliceAssetId The asset id of the slice being instantiated
//! @param entityList All newly instantiated entities will be added to the back of entityList
//! @param parent The EntityId to be assigned as the parent of the slice instance entities.
//! Defaults to an invalid id
//! @return The SliceInstanceAddress of the new slice instance, or an invalid address if operation failed
AZ::SliceComponent::SliceInstanceAddress InstantiateEditorSlice(AZ::Data::AssetId sliceAssetId, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parent = AZ::EntityId());
//! Performs a reparent of entity to newParent. Handles any slice hierarchy manipulation needed
//! @param entity The entity being reparented
//! @param newParent The new parent in the reparent operation
void ReparentEntity(AZ::EntityId& entity, const AZ::EntityId& newParent);
//! Helper that searches for an entityId within a list of entities
//! @param entityId The EntityId being searched for
//! @param entityList the list to search in
//! @return The Entity* whose EntityId matches entityId and is found in entityList.
//! Returns nullptr if not found
static AZ::Entity* FindEntityInList(const AZ::EntityId& entityId, const AZ::SliceComponent::EntityList& entityList);
//! Helper that searches for an entity within the EditorEntityContext
//! @param entityId The entityId being searched for
//! @return The Entity* whose id matches entityId and is found in the EditorEntityContext
//! Returns nullptr if not found
static AZ::Entity* FindEntityInEditor(const AZ::EntityId& entityId);
class SliceOperationValidator
{
public:
SliceOperationValidator();
~SliceOperationValidator();
void SetSerializeContext(AZ::SerializeContext* serializeContext);
//! Clones the provided entities out of the EditorEntityContext and caches them for Compare operations
//! @param entitiesToCapture List of EntityIDs that is used to search the EditorEntityContext and clone the respective Entity* into a cache for Compare operations
//! @return Returns whether the capture was successful
//! Can fail if there is already a cached capture or the entities could not be found in the EditorEntityContext
bool Capture(const AzToolsFramework::EntityIdList& entitiesToCapture);
//! Does a DataPatch compare of the reflected fields of a captured EntityList and the Instantiated entities found in instanceToCompare
//! @param instanceToCompare A SliceInstanceAddress whose InstantiatedContainer will be diffed against a previously captured EntityList using DataPatch
bool Compare(const AZ::SliceComponent::SliceInstanceAddress& instanceToCompare);
//! Resets the current capture so a new one can be made
void Reset();
private:
bool SortCapture(const AzToolsFramework::EntityList& orderToMatch);
AZ::SerializeContext* m_serializeContext;
AZ::SliceComponent::EntityList m_entityStateCapture;
};
class EntityReferenceComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(EntityReferenceComponent, "{3628F6A3-DFAD-4C1E-B9DE-EFBB1B6915C3}");
void Init() override {}
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection);
AZ::EntityId m_entityReference;
};
SliceOperationValidator m_validator;
private:
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
/*
* SliceEditorEntityOwnershipServiceNotificationBus
*/
void OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& ticket) override;
void OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& ticket) override;
/*
* EditorRequestBus
*/
void CreateEditorRepresentation(AZ::Entity* entity) override;
void BrowseForAssets(AzToolsFramework::AssetBrowser::AssetSelectionModel& selection) override { AZ_UNUSED(selection); }
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override { AZ_UNUSED(entityIconPath); return 0; }
bool DisplayHelpersVisible() { return false; }
/*
* AssetSystemRequestBus
*/
const char* GetAbsoluteDevGameFolderPath() override { return ""; }
const char* GetAbsoluteDevRootFolderPath() override { return ""; }
bool GetRelativeProductPathFromFullSourceOrProductPath([[maybe_unused]] const AZStd::string& fullPath, [[maybe_unused]] AZStd::string& relativeProductPath) override { return false; }
bool GetFullSourcePathFromRelativeProductPath([[maybe_unused]] const AZStd::string& relPath, [[maybe_unused]] AZStd::string& fullSourcePath) override { return false; }
bool GetAssetInfoById([[maybe_unused]] const AZ::Data::AssetId& assetId, [[maybe_unused]] const AZ::Data::AssetType& assetType, [[maybe_unused]] const AZStd::string& platformName, [[maybe_unused]] AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& rootFilePath) override { return false; }
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
bool GetSourceInfoBySourceUUID([[maybe_unused]] const AZ::Uuid& sourceUuid, [[maybe_unused]] AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& watchFolder) override { return false; }
bool GetScanFolders([[maybe_unused]] AZStd::vector<AZStd::string>& scanFolders) override { return false; }
bool GetAssetSafeFolders([[maybe_unused]] AZStd::vector<AZStd::string>& assetSafeFolders) override { return false; }
bool IsAssetPlatformEnabled([[maybe_unused]] const char* platform) override { return false; }
int GetPendingAssetsForPlatform([[maybe_unused]] const char* platform) override { return -1; }
bool GetAssetsProducedBySourceUUID([[maybe_unused]] const AZ::Uuid& sourceUuid, [[maybe_unused]] AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo) override { return false; }
AZStd::unique_ptr<testing::NiceMock<AZ::IO::MockFileIOBase>> m_fileIOMock;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZStd::unordered_map<AZ::Data::AssetId, AZStd::vector<AZ::SliceComponent::SliceInstanceAddress>> m_createdSlices;
AZ::Data::AssetId m_newSliceId;
AzFramework::SliceInstantiationTicket m_ticket;
static constexpr const char* m_relativeSourceAssetRoot = "Test/";
};
}