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

2384 lines
110 KiB
C++

/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/UnitTest/ToolsTestApplication.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <QColor>
namespace AzToolsFramework
{
// Used to promote some functions to public so the unit tests can access them.
class EditorLayerComponentTestHelper :
public Layers::EditorLayerComponent
{
public:
AZ_EDITOR_COMPONENT(EditorLayerComponentTestHelper, "{E21CAB65-BFDC-4DFC-B550-A8BF7F235BDA}", Layers::EditorLayerComponent);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorLayerComponentTestHelper, Layers::EditorLayerComponent>()
->Version(1);
}
}
void ClearUnsavedChanges() { m_hasUnsavedChanges = false; }
void SetSaveAsBinary(bool saveAsBinary) { m_editableLayerProperties.m_saveAsBinary = saveAsBinary; }
bool GetSaveAsBinary() const { return m_editableLayerProperties.m_saveAsBinary; }
// Forces a state where a layer is written to a stream with 2 entities with the same ID.
// This allows for testing that the layer load logic safely removes duplicates.
Layers::LayerResult CreateLayerStreamWithDuplicateEntity(
AZStd::vector<AZ::Entity*>& entityList,
AZ::SliceComponent::SliceReferenceToInstancePtrs& layerInstances,
AZ::IO::ByteContainerStream<AZStd::vector<char> >& layerStream,
Layers::EditorLayer& layer)
{
Layers::LayerResult layerPrepareResult = PrepareLayerForSaving(layer, entityList, layerInstances);
if (!layerPrepareResult.IsSuccess())
{
return layerPrepareResult;
}
// Add the first entity in the list twice.
layer.m_layerEntities.push_back(layer.m_layerEntities.front());
return WriteLayerToStream(layer, layerStream);
}
// The unit tests are just testing the ability to save and load layers,
// and not the ability to write this information to disk.
// These functions allow this testing to occur.
Layers::LayerResult PopulateLayerWriteToStreamAndGetEntities(
AZStd::vector<AZ::Entity*>& entityList,
AZ::SliceComponent::SliceReferenceToInstancePtrs& layerInstances,
AZ::IO::ByteContainerStream<AZStd::vector<char> >& layerStream,
Layers::EditorLayer& layer)
{
Layers::LayerResult layerPrepareResult = PrepareLayerForSaving(layer, entityList, layerInstances);
if (!layerPrepareResult.IsSuccess())
{
return layerPrepareResult;
}
return WriteLayerToStream(layer, layerStream);
}
Layers::LayerResult ReadFromLayerStream(
AZ::IO::ByteContainerStream<AZStd::vector<char> >& layerStream,
AZ::SliceComponent::SliceAssetToSliceInstancePtrs& sliceInstances,
AZStd::unordered_map<AZ::EntityId, AZ::Entity*>& uniqueEntities)
{
m_loadedLayer = AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(layerStream);
EXPECT_NE(m_loadedLayer, nullptr);
return PopulateFromLoadedLayerData(*m_loadedLayer, sliceInstances, uniqueEntities);
}
};
struct EntityAndLayerComponent
{
AZ::Entity* m_entity = nullptr;
// Not all functions being tested are available on the ebus, some need direct access.
EditorLayerComponentTestHelper* m_layer = nullptr;
};
bool IsEntityInList(const AZStd::vector<AZ::Entity*>& entityList, AZ::Entity* entity)
{
return AZStd::find(entityList.begin(), entityList.end(), entity) != entityList.end();
}
bool IsEntityInLooseEditorEntities(AZ::Entity* entity)
{
AZStd::vector<AZ::Entity*> editorEntities;
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
editorEntities);
return IsEntityInList(editorEntities, entity);
}
bool IsInstanceAndReferenceInRootSlice(AZ::SliceComponent* rootSlice, AZ::SliceComponent::SliceInstanceAddress sliceInstance)
{
// See if the rootSlice knows about the asset associated with the passed in instance.
// These checks are round about, and not through FindSlice to make sure the root slice's list of references and instances is used.
AZ::SliceComponent::SliceReference* sliceReference = rootSlice->GetSlice(sliceInstance.GetReference()->GetSliceAsset());
if (!sliceReference)
{
return false;
}
// Checking this way makes sure we're using as much data from the rootSlice as possible.
// When moving instances in and out of the root slice, the slice references can change.
return sliceReference->FindInstance(sliceInstance.GetInstance()->GetId()) != nullptr;
}
bool IsLooseEntityInRootSlice(const AZ::SliceComponent* rootSlice, const AZ::EntityId layerEntityId)
{
const EntityList& looseEntities(rootSlice->GetNewEntities());
for (const auto& entity : looseEntities)
{
if (entity->GetId() == layerEntityId)
{
return true;
}
}
return false;
}
AZ::Entity* CreateEditorReadyEntity(const char* entityName)
{
AZ::EntityId createdEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
createdEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntity,
entityName);
EXPECT_TRUE(createdEntityId.IsValid());
AZ::Entity* createdEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(createdEntity, &AZ::ComponentApplicationRequests::FindEntity, createdEntityId);
EXPECT_NE(createdEntity, nullptr);
EXPECT_EQ(createdEntity->GetState(), AZ::Entity::State::Active);
createdEntity->Deactivate();
EXPECT_EQ(createdEntity->GetState(), AZ::Entity::State::Init);
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents,
*createdEntity);
createdEntity->Activate();
EXPECT_EQ(createdEntity->GetState(), AZ::Entity::State::Active);
return createdEntity;
}
EntityAndLayerComponent CreateEntityWithLayer(const char* entityName)
{
EntityAndLayerComponent result;
result.m_entity = CreateEditorReadyEntity(entityName);
result.m_layer = aznew EditorLayerComponentTestHelper();
AZStd::vector<AZ::Component*> newComponents;
newComponents.push_back(result.m_layer);
Components::EditorEntityActionComponent::AddExistingComponentsOutcome componentAddResult(
AZ::Failure(AZStd::string("No listener on AddExistingComponentsToEntity bus.")));
AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(
componentAddResult,
&AzToolsFramework::EntityCompositionRequests::AddExistingComponentsToEntityById,
result.m_entity->GetId(),
newComponents);
EXPECT_TRUE(componentAddResult.IsSuccess());
// Make sure everything is setup correctly.
EXPECT_NE(result.m_entity, nullptr);
EXPECT_NE(result.m_layer, nullptr);
EXPECT_EQ(result.m_entity->GetState(), AZ::Entity::State::Active);
return result;
}
// Filter used to test component copyability.
// Ensure filter passes everything to ensure layer component copyability is unaffected by filter results.
bool DummyComponentFilter([[maybe_unused]] const AZ::SerializeContext::ClassData& classData)
{
return true;
}
class SliceToolsTestApplication : public UnitTest::ToolsTestApplication
{
public:
explicit SliceToolsTestApplication(AZStd::string applicationName)
: ToolsTestApplication(applicationName)
{
}
protected:
bool IsPrefabSystemEnabled() const override
{
return false;
}
};
class EditorLayerComponentTest
: public ::testing::Test
, public UnitTest::TraceBusRedirector
{
protected:
void SetUp() override
{
m_app.Start(m_descriptor);
// 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);
AZ::SerializeContext* context = m_app.GetSerializeContext();
m_editorLayerComponentTestHelperDescriptor = EditorLayerComponentTestHelper::CreateDescriptor();
m_editorLayerComponentTestHelperDescriptor->Reflect(context);
m_layerEntity = CreateEntityWithLayer(m_entityName);
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
void TearDown() override
{
if (m_layerEntity.m_entity)
{
m_layerEntity.m_layer->CleanupLoadedLayer();
}
m_editorLayerComponentTestHelperDescriptor->ReleaseDescriptor();
m_app.Stop();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
// A few tests save a layer and want to check the state after saving.
// A separate unit test actually validates all of the behavior in this function.
Layers::LayerResult SaveMainLayer(Layers::EditorLayer& layerOutput)
{
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
return SaveMainLayer(layerOutput, entitySaveStream);
}
Layers::LayerResult SaveMainLayer(
Layers::EditorLayer& layerOutput,
AZ::IO::ByteContainerStream<AZStd::vector<char> > &streamOutput,
AZStd::vector<AZ::Entity*>& layerEntities,
AZ::SliceComponent::SliceReferenceToInstancePtrs& instancesInLayers)
{
return m_layerEntity.m_layer->PopulateLayerWriteToStreamAndGetEntities(
layerEntities,
instancesInLayers,
streamOutput,
layerOutput);
}
Layers::LayerResult SaveMainLayer(
Layers::EditorLayer& layerOutput,
AZ::IO::ByteContainerStream<AZStd::vector<char> > &streamOutput)
{
AZStd::vector<AZ::Entity*> layerEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers;
return SaveMainLayer(layerOutput, streamOutput, layerEntities, instancesInLayers);
}
AZ::SliceComponent::SliceInstanceAddress CreateSliceInstanceFromSlice(
AZ::SliceComponent* rootSlice,
const AZ::Data::Asset<AZ::SliceAsset>& sliceAsset)
{
EXPECT_NE(rootSlice, nullptr);
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = rootSlice->AddSlice(sliceAsset);
AZ::Entity* instanceEntity = GetEntityFromSliceInstance(instantiatedSlice);
instanceEntity->Init();
EXPECT_EQ(instanceEntity->GetState(), AZ::Entity::State::Init);
instanceEntity->Activate();
EXPECT_EQ(instanceEntity->GetState(), AZ::Entity::State::Active);
return instantiatedSlice;
}
// Creates a slice instance, used to test serialization with layers.
// Note that DeleteSliceInstance must be called.
AZ::SliceComponent::SliceInstanceAddress CreateSliceInstance()
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
AZ::Entity* sliceEntity = aznew AZ::Entity("SliceEntity");
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app.GetSerializeContext());
AZ::Entity* entityInSlice = aznew AZ::Entity("EntityInSlice");
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents,
*entityInSlice);
EntityList entitiesToAddToSlice;
entitiesToAddToSlice.push_back(entityInSlice);
sliceComponent->AddEntities(entitiesToAddToSlice);
AZStd::string sliceAssetFile("Temp/GeneratedSlices/LayerTestSlice.slice");
AZ::Data::AssetId assetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetId,
&AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
sliceAssetFile.c_str(),
azrtti_typeid<AZ::SliceAsset>(),
true);
sliceAsset.Create(assetId, false);
sliceAsset.Get()->SetData(sliceEntity, sliceComponent, false);
sliceComponent->Instantiate();
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
return CreateSliceInstanceFromSlice(rootSlice, sliceAsset);
}
AZ::Entity* GetEntityFromSliceInstance(AZ::SliceComponent::SliceInstanceAddress instantiatedSlice)
{
EXPECT_TRUE(instantiatedSlice.IsValid());
EXPECT_NE(instantiatedSlice.GetInstance(), nullptr);
EXPECT_NE(instantiatedSlice.GetInstance()->GetInstantiated(), nullptr);
EXPECT_EQ(instantiatedSlice.GetInstance()->GetInstantiated()->m_entities.size(), 1);
return instantiatedSlice.GetInstance()->GetInstantiated()->m_entities[0];
}
void DeleteSliceInstance(AZ::SliceComponent::SliceInstanceAddress instance)
{
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
rootSlice->RemoveSliceInstance(instance);
}
const char* m_entityName = "LayerEntityName";
SliceToolsTestApplication m_app{ "EditorLayerComponentTest" };
EntityAndLayerComponent m_layerEntity;
AZ::ComponentApplication::Descriptor m_descriptor;
AZ::ComponentDescriptor* m_editorLayerComponentTestHelperDescriptor = nullptr;
};
TEST_F(EditorLayerComponentTest, LayerTests_EntityCreatedWithLayer_HasLayerReturnsTrue)
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
EXPECT_TRUE(isLayerEntity);
}
TEST_F(EditorLayerComponentTest, LayerTests_GetLayerColor_ReturnsCorrectColor)
{
// Layers serialize color as an AZ::Color because it works with our serialization system,
// but GetColor returns a QColor because it works with our UI system to render with the color.
// Alpha is not tested because layers don't use alpha.
AZ::Color setLayerColor(aznumeric_cast<uint8_t>(255), aznumeric_cast<uint8_t>(128), aznumeric_cast<uint8_t>(64), aznumeric_cast<uint8_t>(255));
m_layerEntity.m_layer->SetLayerColor(setLayerColor);
// Set the get color to specifically not be the same as the set, so we know if the ebus
// was connected and worked.
QColor getLayerColor(0, 0, 0);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
getLayerColor,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
EXPECT_EQ(getLayerColor.red(), setLayerColor.GetR8());
EXPECT_EQ(getLayerColor.green(), setLayerColor.GetG8());
EXPECT_EQ(getLayerColor.blue(), setLayerColor.GetB8());
}
TEST_F(EditorLayerComponentTest, LayerTests_SingleLayer_LayerNameIsValid)
{
bool isLayerNameValid = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerNameValid,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsLayerNameValid);
EXPECT_TRUE(isLayerNameValid);
}
TEST_F(EditorLayerComponentTest, LayerTests_TwoLayersUniqueNames_LayerNameIsValid)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer("UniqueLayerName");
bool isLayerNameValid = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerNameValid,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsLayerNameValid);
EXPECT_TRUE(isLayerNameValid);
}
TEST_F(EditorLayerComponentTest, LayerTests_TwoLayersConflictingNames_LayerNameIsNotValid)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer(m_entityName);
bool isLayerNameValid = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerNameValid,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsLayerNameValid);
EXPECT_FALSE(isLayerNameValid);
}
TEST_F(EditorLayerComponentTest, LayerTests_TwoLayersConflictingNames_LayerNameConflictingMappingShowsConflict)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer(m_entityName);
AZStd::unordered_set<AZ::EntityId> layerEntities;
layerEntities.insert(m_layerEntity.m_entity->GetId());
layerEntities.insert(secondLayer.m_entity->GetId());
AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
for (AZ::EntityId layerEntityId : layerEntities)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
nameConflictMapping);
}
EXPECT_EQ(nameConflictMapping[m_entityName], 2);
}
TEST_F(EditorLayerComponentTest, LayerTests_TwoLayersSameNameDifferentCase_LayerNameConflictingMappingShowsConflict)
{
AZStd::string upperCaseName = m_entityName;
AZStd::to_upper(upperCaseName.begin(), upperCaseName.end());
// Verify that the casing actually changed. This catches if someone adjusts the
// default layer name in these layer tests to be all upper case.
EXPECT_NE(upperCaseName.compare(m_entityName), 0);
EntityAndLayerComponent secondLayer = CreateEntityWithLayer(upperCaseName.c_str());
AZStd::unordered_set<AZ::EntityId> layerEntities;
layerEntities.insert(m_layerEntity.m_entity->GetId());
layerEntities.insert(secondLayer.m_entity->GetId());
AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
for (AZ::EntityId layerEntityId : layerEntities)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
nameConflictMapping);
}
EXPECT_EQ(nameConflictMapping[m_entityName], 2);
}
TEST_F(EditorLayerComponentTest, LayerTests_TwoLayersUniqueNames_NameConflictMappingHasNoConflicts)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer("UniqueLayerName");
AZStd::unordered_set<AZ::EntityId> layerEntities;
layerEntities.insert(m_layerEntity.m_entity->GetId());
layerEntities.insert(secondLayer.m_entity->GetId());
AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
for (AZ::EntityId layerEntityId : layerEntities)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
nameConflictMapping);
}
EXPECT_EQ(nameConflictMapping.find(m_entityName), nameConflictMapping.end());
}
// The design of this is expected to change, but this unit test validates current behavior.
// Eventually we would like to decouple the layer name from the entity name.
TEST_F(EditorLayerComponentTest, LayerTests_LayerWithNoParent_FileNameIsEntityName)
{
AZ::Outcome<AZStd::string, AZStd::string> layerFileNameResult(
AZ::Failure(AZStd::string("No ebus listener available for this layer.")));
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerFileNameResult,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerBaseFileName);
EXPECT_TRUE(layerFileNameResult.IsSuccess());
EXPECT_EQ(layerFileNameResult.GetValue().compare(m_entityName), 0);
}
TEST_F(EditorLayerComponentTest, LayerTests_LayerWithParent_FileNameIsParentDotEntityName)
{
const AZStd::string parentName = "AParentLayer";
EntityAndLayerComponent parentLayer = CreateEntityWithLayer(parentName.c_str());
AZ::TransformBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
parentLayer.m_entity->GetId());
AZ::Outcome<AZStd::string, AZStd::string> layerFileNameResult(
AZ::Failure(AZStd::string("No ebus listener available for this layer.")));
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerFileNameResult,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerBaseFileName);
EXPECT_TRUE(layerFileNameResult.IsSuccess());
AZStd::string expectedParentName = parentName + "." + m_entityName;
EXPECT_EQ(layerFileNameResult.GetValue().compare(expectedParentName), 0);
}
TEST_F(EditorLayerComponentTest, LayerTests_SetVisibility_GetVisibilityReturnsCorrectValue)
{
bool currentVisibility = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
currentVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_EQ(currentVisibility, true);
bool flippedVisibility = !currentVisibility;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
flippedVisibility);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
currentVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_EQ(flippedVisibility, currentVisibility);
// Change the visibility again to make sure setting it to both values works.
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
!flippedVisibility);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
currentVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_EQ(!flippedVisibility, currentVisibility);
}
TEST_F(EditorLayerComponentTest, LayerTests_NewLayer_HasUnsavedChanges)
{
bool hasUnsavedChanges = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
hasUnsavedChanges,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
EXPECT_TRUE(hasUnsavedChanges);
}
TEST_F(EditorLayerComponentTest, LayerTests_SavedLayer_DoesNotHaveUnsavedChanges)
{
m_layerEntity.m_layer->ClearUnsavedChanges();
bool hasUnsavedChanges = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
hasUnsavedChanges,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
EXPECT_FALSE(hasUnsavedChanges);
}
TEST_F(EditorLayerComponentTest, LayerTests_AddedEntityToLayer_HasUnsavedChanges)
{
m_layerEntity.m_layer->ClearUnsavedChanges();
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
bool hasUnsavedChanges = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
hasUnsavedChanges,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
EXPECT_TRUE(hasUnsavedChanges);
}
TEST_F(EditorLayerComponentTest, LayerTests_ModifiedEntityInLayer_HasUnsavedChanges)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
m_layerEntity.m_layer->ClearUnsavedChanges();
// Change the scale of the child entity so it registers as an unsaved change on the layer.
float scale = 0.0f;
AZ::TransformBus::EventResult(
scale,
childEntity->GetId(),
&AZ::TransformBus::Events::GetLocalUniformScale);
scale += 1.0f;
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetLocalUniformScale,
scale);
bool hasUnsavedChanges = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
hasUnsavedChanges,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
EXPECT_TRUE(hasUnsavedChanges);
}
TEST_F(EditorLayerComponentTest, LayerTests_CanParentChangeToNonLayer_ReturnsFalse)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
bool canParentChange = true;
AZ::TransformNotificationBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformNotificationBus::Events::CanParentChange,
canParentChange,
AZ::EntityId(),
childEntity->GetId());
EXPECT_FALSE(canParentChange);
}
TEST_F(EditorLayerComponentTest, LayerTests_CanParentChangeToLayer_ReturnsTrue)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer("UniqueLayerName");
// Most tests around ebuses, the value is initialized to the opposite of the expected result.
// This particular message is an aggregate message that goes out to multiple listeners,
// and any of them can decline the parent change. This means that the initial
// value needs to start true.
bool canParentChange = true;
AZ::TransformNotificationBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformNotificationBus::Events::CanParentChange,
canParentChange,
AZ::EntityId(),
secondLayer.m_entity->GetId());
EXPECT_TRUE(canParentChange);
}
TEST_F(EditorLayerComponentTest, LayerTests_CanParentChangeFromLayerToInvalid_ReturnsTrue)
{
EntityAndLayerComponent secondLayer = CreateEntityWithLayer("UniqueLayerName");
AZ::TransformBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
secondLayer.m_entity->GetId());
bool canParentChange = true;
AZ::TransformNotificationBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformNotificationBus::Events::CanParentChange,
canParentChange,
secondLayer.m_entity->GetId(),
AZ::EntityId());
EXPECT_TRUE(canParentChange);
}
TEST_F(EditorLayerComponentTest, LayerTests_AttemptToSetParentToNonLayer_ParentDoesNotChange)
{
AZ::Entity* nonLayerEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
nonLayerEntity->GetId());
AZ::EntityId parentId = nonLayerEntity->GetId();
AZ::TransformBus::EventResult(
parentId,
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(parentId, AZ::EntityId());
}
TEST_F(EditorLayerComponentTest, LayerTests_VisibleLayerVisibleChild_BothVisible)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
bool layerChildrenVisibility = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerChildrenVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool isChildVisible = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildVisible,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
bool isChildVisibilityFlagSet = false;
AzToolsFramework::EditorVisibilityRequestBus::EventResult(
isChildVisibilityFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::GetVisibilityFlag);
EXPECT_TRUE(layerChildrenVisibility);
EXPECT_TRUE(isChildVisible);
EXPECT_TRUE(isChildVisibilityFlagSet);
}
TEST_F(EditorLayerComponentTest, LayerTests_InvisibleLayerVisibleChild_BothInvisibleChildPreservesVisibility)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
// This is necessary to prevent a warning in the undo system.
// This unit test had modified an entity to make it dirty, so it needs to be marked as such.
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity,
m_layerEntity.m_entity->GetId());
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
bool layerChildrenVisibility = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerChildrenVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool isChildVisible = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildVisible,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
bool isChildVisibilityFlagSet = false;
AzToolsFramework::EditorVisibilityRequestBus::EventResult(
isChildVisibilityFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::GetVisibilityFlag);
EXPECT_FALSE(layerChildrenVisibility);
EXPECT_FALSE(isChildVisible);
EXPECT_TRUE(isChildVisibilityFlagSet);
}
// Layer visibility toggle only works one way: Invisible layers make all children invisible, but
// visible layers do not force children to be visible.
TEST_F(EditorLayerComponentTest, LayerTests_VisibleLayerInvisibleChild_ChildIsInvisible)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::EditorVisibilityRequestBus::Event(
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::SetVisibilityFlag,
false);
bool layerChildrenVisibility = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerChildrenVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool isChildVisible = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildVisible,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
bool isChildVisibilityFlagSet = true;
AzToolsFramework::EditorVisibilityRequestBus::EventResult(
isChildVisibilityFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::GetVisibilityFlag);
EXPECT_TRUE(layerChildrenVisibility);
EXPECT_FALSE(isChildVisible);
EXPECT_FALSE(isChildVisibilityFlagSet);
}
TEST_F(EditorLayerComponentTest, LayerTests_InvisibleLayerInvisibleChild_BothInvisibleChildPreservesVisibility)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity,
m_layerEntity.m_entity->GetId());
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::EditorVisibilityRequestBus::Event(
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::SetVisibilityFlag,
false);
bool layerChildrenVisibility = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerChildrenVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool isChildVisible = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildVisible,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
bool isChildVisibilityFlagSet = true;
AzToolsFramework::EditorVisibilityRequestBus::EventResult(
isChildVisibilityFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::GetVisibilityFlag);
EXPECT_FALSE(layerChildrenVisibility);
EXPECT_FALSE(isChildVisible);
EXPECT_FALSE(isChildVisibilityFlagSet);
}
TEST_F(EditorLayerComponentTest, LayerTests_InvisLayerVisLayerVisChild_AllInvisiblePreservingVisibility)
{
const AZStd::string parentName = "AParentLayer";
EntityAndLayerComponent parentLayer = CreateEntityWithLayer(parentName.c_str());
AZ::TransformBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
parentLayer.m_entity->GetId());
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
parentLayer.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity,
parentLayer.m_entity->GetId());
bool parentLayerChildrenVisible = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
parentLayerChildrenVisible,
parentLayer.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool layerChildrenVisibility = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerChildrenVisibility,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
bool isChildVisible = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildVisible,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
bool isChildVisibilityFlagSet = false;
AzToolsFramework::EditorVisibilityRequestBus::EventResult(
isChildVisibilityFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorVisibilityRequestBus::Events::GetVisibilityFlag);
EXPECT_FALSE(parentLayerChildrenVisible);
EXPECT_TRUE(layerChildrenVisibility);
EXPECT_FALSE(isChildVisible);
EXPECT_TRUE(isChildVisibilityFlagSet);
}
TEST_F(EditorLayerComponentTest, LayerTests_UnlockedLayerUnlockedChild_BothUnlocked)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
bool isLayerLocked = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isLayerLocked,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockFlagSet = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockedViaHierarchy = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockedViaHierarchy,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsLocked);
EXPECT_FALSE(isLayerLocked);
EXPECT_FALSE(isChildLockFlagSet);
EXPECT_FALSE(isChildLockedViaHierarchy);
}
TEST_F(EditorLayerComponentTest, LayerTests_LockedLayerUnlockedChild_ChildLockedAndPreservesLockState)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::EditorLockComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorLockComponentRequests::SetLocked,
true);
bool isLayerLocked = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isLayerLocked,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockFlagSet = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockedViaHierarchy = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockedViaHierarchy,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsLocked);
EXPECT_TRUE(isLayerLocked);
EXPECT_FALSE(isChildLockFlagSet);
EXPECT_TRUE(isChildLockedViaHierarchy);
}
TEST_F(EditorLayerComponentTest, LayerTests_LockedLayerLockedChild_AllLocked)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::EditorLockComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorLockComponentRequests::SetLocked,
true);
AzToolsFramework::EditorLockComponentRequestBus::Event(
childEntity->GetId(),
&AzToolsFramework::EditorLockComponentRequests::SetLocked,
true);
bool isLayerLocked = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isLayerLocked,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockFlagSet = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockedViaHierarchy = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockedViaHierarchy,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsLocked);
EXPECT_TRUE(isLayerLocked);
EXPECT_TRUE(isChildLockFlagSet);
EXPECT_TRUE(isChildLockedViaHierarchy);
}
TEST_F(EditorLayerComponentTest, LayerTests_LockLayerUnlockLayerUnlockChild_AllLockedPreservingVisibility)
{
const AZStd::string parentName = "AParentLayer";
EntityAndLayerComponent parentLayer = CreateEntityWithLayer(parentName.c_str());
AZ::TransformBus::Event(
m_layerEntity.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
parentLayer.m_entity->GetId());
AZ::Entity* childEntity = CreateEditorReadyEntity("NonLayerEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AzToolsFramework::EditorLockComponentRequestBus::Event(
parentLayer.m_entity->GetId(),
&AzToolsFramework::EditorLockComponentRequests::SetLocked,
true);
bool isParentLocked = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isParentLocked,
parentLayer.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isLayerLockFlagSet = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isLayerLockFlagSet,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isLayerLockedViaHierarchy = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isLayerLockedViaHierarchy,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsLocked);
bool isChildLockFlagSet = true;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockFlagSet,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsJustThisEntityLocked);
bool isChildLockedViaHierarchy = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isChildLockedViaHierarchy,
childEntity->GetId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsLocked);
EXPECT_TRUE(isParentLocked);
EXPECT_TRUE(isLayerLockedViaHierarchy);
EXPECT_FALSE(isLayerLockFlagSet);
EXPECT_TRUE(isLayerLockedViaHierarchy);
EXPECT_FALSE(isChildLockFlagSet);
EXPECT_TRUE(isChildLockedViaHierarchy);
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveEmptyLayer_SavesWithoutError)
{
// SaveMainLayer is not called here because this test is testing behavior
// that SaveMainLayer assumes will work.
AZStd::vector<AZ::Entity*> layerEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
Layers::EditorLayer layer;
Layers::LayerResult layerResult = m_layerEntity.m_layer->PopulateLayerWriteToStreamAndGetEntities(
layerEntities,
instancesInLayers,
entitySaveStream,
layer);
EXPECT_TRUE(layerResult.IsSuccess());
EXPECT_EQ(layer.m_layerEntities.size(), 0);
EXPECT_EQ(layer.m_sliceAssetsToSliceInstances.size(), 0);
}
// To minimize the need to modify the level file, the color should save to the layer
// and not the level. This is tested by verifying the color is set to zero on the
// layer component after the save, and the color is set correctly in the EditorLayer object.
TEST_F(EditorLayerComponentTest, LayerTests_SaveColorModified_ColorSavedCorrectly)
{
AZ::Color setLayerColor(aznumeric_cast<uint8_t>(64), aznumeric_cast<uint8_t>(255), aznumeric_cast<uint8_t>(128), aznumeric_cast<uint8_t>(255));
m_layerEntity.m_layer->SetLayerColor(setLayerColor);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
QColor getLayerColor(2, 4, 8);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
getLayerColor,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
EXPECT_EQ(getLayerColor.red(), 255);
EXPECT_EQ(getLayerColor.green(), 255);
EXPECT_EQ(getLayerColor.blue(), 255);
EXPECT_EQ(layer.m_layerProperties.m_color.GetR8(), setLayerColor.GetR8());
EXPECT_EQ(layer.m_layerProperties.m_color.GetG8(), setLayerColor.GetG8());
EXPECT_EQ(layer.m_layerProperties.m_color.GetB8(), setLayerColor.GetB8());
}
TEST_F(EditorLayerComponentTest, LayerTests_ColorModifiedRestoreLayerAfterSave_ColorRestoresCorrectly)
{
AZ::Color setLayerColor(aznumeric_cast<uint8_t>(10), aznumeric_cast<uint8_t>(30), aznumeric_cast<uint8_t>(20), aznumeric_cast<uint8_t>(255));
m_layerEntity.m_layer->SetLayerColor(setLayerColor);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
QColor getLayerColor(2, 4, 8);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
getLayerColor,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
EXPECT_EQ(getLayerColor.red(), setLayerColor.GetR8());
EXPECT_EQ(getLayerColor.green(), setLayerColor.GetG8());
EXPECT_EQ(getLayerColor.blue(), setLayerColor.GetB8());
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveFormatModifiedRestoreLayerAfterSave_SaveFormatRestoresCorrectly)
{
// Set the save format to binary, as the default is xml.
AzToolsFramework::Layers::LayerProperties::SaveFormat setSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Binary;
m_layerEntity.m_layer->SetSaveFormat(setSaveFormat);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
// Check the save format is still binary after a restoring the layer.
AzToolsFramework::Layers::LayerProperties::SaveFormat getSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Xml;
bool saveFormatIsBinary = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
saveFormatIsBinary,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsSaveFormatBinary);
if (saveFormatIsBinary)
{
getSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Binary;
}
EXPECT_EQ(getSaveFormat, setSaveFormat);
// Now change the save format back to xml and redo the test.
setSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Xml;
m_layerEntity.m_layer->SetSaveFormat(setSaveFormat);
saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
getSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Xml;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
saveFormatIsBinary,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsSaveFormatBinary);
if (saveFormatIsBinary)
{
getSaveFormat = AzToolsFramework::Layers::LayerProperties::SaveFormat::Binary;
}
EXPECT_EQ(getSaveFormat, setSaveFormat);
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadColorModified_ColorLoadsCorrectly)
{
AZ::Color savedLayerColor(aznumeric_cast<uint8_t>(6), aznumeric_cast<uint8_t>(7), aznumeric_cast<uint8_t>(8), aznumeric_cast<uint8_t>(255));
m_layerEntity.m_layer->SetLayerColor(savedLayerColor);
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream);
EXPECT_TRUE(saveResult.IsSuccess());
// After saving the layer, restore the editor data and set the color to something else.
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZ::Color unsavedLayerColor(aznumeric_cast<uint8_t>(20), aznumeric_cast<uint8_t>(30), aznumeric_cast<uint8_t>(40), aznumeric_cast<uint8_t>(255));
m_layerEntity.m_layer->SetLayerColor(unsavedLayerColor);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs sliceInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, sliceInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
QColor getLayerColor(2, 4, 8);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
getLayerColor,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
EXPECT_FLOAT_EQ(aznumeric_cast<float>(getLayerColor.redF()), savedLayerColor.GetR());
EXPECT_FLOAT_EQ(aznumeric_cast<float>(getLayerColor.greenF()), savedLayerColor.GetG());
EXPECT_FLOAT_EQ(aznumeric_cast<float>(getLayerColor.blueF()), savedLayerColor.GetB());
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveLayerVisibilityModified_VisibilitySavesCorrectly)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
bool visibilityAfterSave = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
visibilityAfterSave,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_TRUE(visibilityAfterSave);
EXPECT_FALSE(layer.m_layerProperties.m_isLayerVisible);
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerAfterSave_VisibilityRestoresCorrectly)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
bool visibilityAfterSave = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
visibilityAfterSave,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_FALSE(visibilityAfterSave);
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadVisibilityModified_VisibilityLoadsCorrectly)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
false);
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream);
EXPECT_TRUE(saveResult.IsSuccess());
// After saving the layer, restore the editor data and set the visibility to true, the value that wasn't saved.
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetLayerChildrenVisibility,
true);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs sliceInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, sliceInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
bool visibilityAfterLoad = true;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
visibilityAfterLoad,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AreLayerChildrenVisible);
EXPECT_FALSE(visibilityAfterLoad);
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveLayerBinaryModified_BinarySavesCorrectly)
{
m_layerEntity.m_layer->SetSaveAsBinary(true);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_FALSE(m_layerEntity.m_layer->GetSaveAsBinary());
EXPECT_TRUE(layer.m_layerProperties.m_saveAsBinary);
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerAfterSave_BinaryRestoresCorrectly)
{
m_layerEntity.m_layer->SetSaveAsBinary(true);
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer);
EXPECT_TRUE(saveResult.IsSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
EXPECT_TRUE(m_layerEntity.m_layer->GetSaveAsBinary());
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadBinarySaveTypeModified_BinarySaveTypeLoadsCorrectly)
{
m_layerEntity.m_layer->SetSaveAsBinary(true);
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream);
EXPECT_TRUE(saveResult.IsSuccess());
// After saving the layer, restore the editor data and set the save as binary to false, the value that wasn't saved.
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
m_layerEntity.m_layer->SetSaveAsBinary(false);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs sliceInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, sliceInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_TRUE(m_layerEntity.m_layer->GetSaveAsBinary());
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadEntityInLayer_EntityLoadsCorrectly)
{
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 1);
EXPECT_NE(savedEntities[0], nullptr);
EXPECT_EQ(savedEntities[0]->GetId(), childEntity->GetId());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(uniqueEntities.size(), 1);
EXPECT_NE(uniqueEntities.begin(), uniqueEntities.end());
EXPECT_EQ(uniqueEntities.begin()->first, childEntity->GetId());
EXPECT_NE(uniqueEntities.begin()->second, nullptr);
EXPECT_EQ(uniqueEntities.begin()->second->GetId(), childEntity->GetId());
}
TEST_F(EditorLayerComponentTest, LayerTests_RootSliceEntityEraseRestore_EntitiesRemovedAndRestoredCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 1);
EXPECT_NE(savedEntities[0], nullptr);
EXPECT_EQ(savedEntities[0]->GetId(), childEntity->GetId());
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
// First, verify that childEntity is in the loose entity list.
// Size of 2 is the layer and the child entity.
EXPECT_TRUE(IsEntityInLooseEditorEntities(childEntity));
// After erasing the saved entities, verify childEntity is no longer a loose entity.
rootSlice->EraseEntities(savedEntities);
EXPECT_FALSE(IsEntityInLooseEditorEntities(childEntity));
rootSlice->ReplaceEntities(savedEntities);
EXPECT_TRUE(IsEntityInLooseEditorEntities(childEntity));
}
TEST_F(EditorLayerComponentTest, LayerTests_NestedLayersDoNotSaveLayersInLayers_LayersLoadCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZStd::string childLayerName = "ChildLayerName";
EntityAndLayerComponent childLayer = CreateEntityWithLayer(childLayerName.c_str());
AZ::TransformBus::Event(
childLayer.m_entity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 0);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(uniqueEntities.size(), 0);
}
// With a hierarchy of: LayerEntityName -> ChildLayer -> ChildEntity, when the parent and child layer are saved,
// then loaded, the ChildEntity should be saved in the ChildLayer and not the LayerEntityName, and the ChildLayer should not
// be saved to the LayerEntityName.
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadLayerLayerEntityHierarchy_LayersAndEntityLoadCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZStd::string childLayerName = "ChildLayer";
EntityAndLayerComponent childLayerEntityAndComponent = CreateEntityWithLayer(childLayerName.c_str());
AZ::EntityId childLayerEntityId = childLayerEntityAndComponent.m_entity->GetId();
AZ::TransformBus::Event(
childLayerEntityId,
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
childLayerEntityId);
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 0);
Layers::EditorLayer childLayer;
AZStd::vector<char> childLayerEntitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > childLayerEntitySaveStream(&childLayerEntitySaveBuffer);
AZStd::vector<AZ::Entity*> childLayerSavedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs childLayerSavedInstances;
Layers::LayerResult childSaveResult = childLayerEntityAndComponent.m_layer->PopulateLayerWriteToStreamAndGetEntities(
childLayerSavedEntities,
childLayerSavedInstances,
childLayerEntitySaveStream,
childLayer);
EXPECT_TRUE(childSaveResult.IsSuccess());
EXPECT_EQ(childLayerSavedEntities.size(), 1);
EXPECT_NE(childLayerSavedEntities[0], nullptr);
EXPECT_EQ(childLayerSavedEntities[0]->GetId(), childEntity->GetId());
// Restore the cached editor data for both the parent and child layers.
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
childLayerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
// Verify that the outermost layer loads correctly and has no entities in it.
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(uniqueEntities.size(), 0);
// Verify the inner layer loads correctly and has the entity in it we are looking for.
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueChildEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedChildInstances;
Layers::LayerResult childReadResult =
childLayerEntityAndComponent.m_layer->ReadFromLayerStream(childLayerEntitySaveStream, loadedChildInstances, uniqueChildEntities);
EXPECT_TRUE(childReadResult.IsSuccess());
EXPECT_EQ(uniqueChildEntities.size(), 1);
EXPECT_NE(uniqueChildEntities.begin(), uniqueChildEntities.end());
EXPECT_EQ(uniqueChildEntities.begin()->first, childEntity->GetId());
EXPECT_NE(uniqueChildEntities.begin()->second, nullptr);
EXPECT_EQ(uniqueChildEntities.begin()->second->GetId(), childEntity->GetId());
childLayerEntityAndComponent.m_layer->CleanupLoadedLayer();
}
TEST_F(EditorLayerComponentTest, LayerTests_SliceInstanceAddedToLayer_LayerHasUnsavedChanges)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
m_layerEntity.m_layer->ClearUnsavedChanges();
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
bool hasUnsavedChanges = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
hasUnsavedChanges,
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
EXPECT_TRUE(hasUnsavedChanges);
DeleteSliceInstance(instantiatedSlice);
}
TEST_F(EditorLayerComponentTest, LayerTests_SaveAndLoadInstanceInLayer_InstanceLoadsCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 0);
EXPECT_EQ(savedInstances.size(), 1);
AZ::SliceComponent::SliceReferenceToInstancePtrs::iterator refToInstanceIterator =
savedInstances.find(instantiatedSlice.GetReference());
EXPECT_NE(refToInstanceIterator, savedInstances.end());
EXPECT_NE(refToInstanceIterator->second.find(instantiatedSlice.GetInstance()), refToInstanceIterator->second.end());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(uniqueEntities.size(), 0);
EXPECT_EQ(loadedInstances.size(), 1);
EXPECT_NE(loadedInstances.find(instantiatedSlice.GetReference()->GetSliceAsset()), loadedInstances.end());
DeleteSliceInstance(instantiatedSlice);
}
// Temporarily disabled until fix provided in mainline - instantiatedSlice is no longer valid to access after RemoveAndCacheInstances
/*
TEST_F(EditorLayerComponentTest, LayerTests_SliceInstanceOnlyInLayerRootSliceRemoveRestore_InstanceRemovedAndRestoredCorrectly)
{
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 0);
EXPECT_EQ(savedInstances.size(), 1);
AZ::SliceComponent::SliceReferenceToInstancePtrs::iterator refToInstanceIterator =
savedInstances.find(instantiatedSlice.GetReference());
EXPECT_NE(refToInstanceIterator, savedInstances.end());
EXPECT_NE(refToInstanceIterator->second.find(instantiatedSlice.GetInstance()), refToInstanceIterator->second.end());
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
// First, verify the the instance is in the root slice.
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
// After removing the instances, verify the instance and reference is no longer in the root slice.
rootSlice->RemoveAndCacheInstances(savedInstances);
EXPECT_FALSE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
rootSlice->RestoreCachedInstances();
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
DeleteSliceInstance(instantiatedSlice);
}
// Tests that the slice reference remains in the root scene if there is an instance of the slice in the layer and in the root scene.
TEST_F(EditorLayerComponentTest, LayerTests_MultipleSliceInstanceInLayerRootSliceRemoveRestore_InstanceRemovedAndRestoredCorrectly)
{
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
// Put the first instance in the layer
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
// Create a second slice instance that is not in the layer.
AZ::SliceComponent::SliceInstanceAddress secondSliceInstance = CreateSliceInstanceFromSlice(
rootSlice,
instantiatedSlice.GetReference()->GetSliceAsset());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 0);
EXPECT_EQ(savedInstances.size(), 1);
AZ::SliceComponent::SliceReferenceToInstancePtrs::iterator refToInstanceIterator =
savedInstances.find(instantiatedSlice.GetReference());
EXPECT_NE(refToInstanceIterator, savedInstances.end());
EXPECT_NE(refToInstanceIterator->second.find(instantiatedSlice.GetInstance()), refToInstanceIterator->second.end());
// Make sure the second slice instance is not saved.
EXPECT_EQ(refToInstanceIterator->second.find(secondSliceInstance.GetInstance()), refToInstanceIterator->second.end());
// First, verify the the instances are in the root slice.
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, secondSliceInstance));
// After removing the instances, verify the first instance is no longer in the root slice, but the reference and second instance are.
rootSlice->RemoveAndCacheInstances(savedInstances);
EXPECT_FALSE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
AZ::SliceComponent::SliceReference* sliceReference = rootSlice->GetSlice(instantiatedSlice.GetReference()->GetSliceAsset());
EXPECT_NE(sliceReference, nullptr);
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, secondSliceInstance));
rootSlice->RestoreCachedInstances();
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, secondSliceInstance));
DeleteSliceInstance(instantiatedSlice);
DeleteSliceInstance(secondSliceInstance);
}
*/
TEST_F(EditorLayerComponentTest, LayerTests_DuplicateEntitiesInSceneAndLayer_DuplicateEntityIsDeleted)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 1);
EXPECT_NE(savedEntities[0], nullptr);
EXPECT_EQ(savedEntities[0]->GetId(), childEntity->GetId());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
// ReadFromLayerStream will delete the older entities, so create an entity here that can be safely deleted.
AZ::Entity* duplicateToBeDeletedEntity = aznew AZ::Entity(childEntity->GetId(), "GoingToBeDeleted");
uniqueEntities[childEntity->GetId()] = duplicateToBeDeletedEntity;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(uniqueEntities.size(), 1);
EXPECT_NE(uniqueEntities.begin(), uniqueEntities.end());
EXPECT_EQ(uniqueEntities.begin()->first, childEntity->GetId());
EXPECT_NE(uniqueEntities.begin()->second, nullptr);
EXPECT_EQ(uniqueEntities.begin()->second->GetId(), childEntity->GetId());
EXPECT_NE(uniqueEntities.begin()->second, duplicateToBeDeletedEntity);
}
TEST_F(EditorLayerComponentTest, LayerTests_DuplicateEntitiesInLayer_DuplicateEntityIsDeleted)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
m_layerEntity.m_entity->GetId());
Layers::EditorLayer layer;
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::LayerResult saveResult =
m_layerEntity.m_layer->CreateLayerStreamWithDuplicateEntity(
savedEntities,
savedInstances,
entitySaveStream,
layer);
EXPECT_TRUE(saveResult.IsSuccess());
EXPECT_EQ(savedEntities.size(), 1);
EXPECT_NE(savedEntities[0], nullptr);
EXPECT_EQ(savedEntities[0]->GetId(), childEntity->GetId());
EXPECT_EQ(layer.m_layerEntities.size(), 2);
// Make sure the saved data has a duplicate entity in it.
EXPECT_EQ(layer.m_layerEntities[0]->GetId(), layer.m_layerEntities[1]->GetId());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> uniqueEntities;
AZ::SliceComponent::SliceAssetToSliceInstancePtrs loadedInstances;
Layers::LayerResult readResult = m_layerEntity.m_layer->ReadFromLayerStream(entitySaveStream, loadedInstances, uniqueEntities);
EXPECT_TRUE(readResult.IsSuccess());
// Verify there is only one entity loaded from the layer.
EXPECT_EQ(uniqueEntities.size(), 1);
EXPECT_NE(uniqueEntities.begin(), uniqueEntities.end());
EXPECT_EQ(uniqueEntities.begin()->first, childEntity->GetId());
EXPECT_NE(uniqueEntities.begin()->second, nullptr);
EXPECT_EQ(uniqueEntities.begin()->second->GetId(), childEntity->GetId());
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreNullLayer_FailsToRestore)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(nullptr, "RecoveredLayerName", invalidParentId);
EXPECT_FALSE(recoveryResult.IsSuccess());
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreEmptyLayer_RestoresCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
// Check that the layer is in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Delete the layer from the scene.
bool layerDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
layerDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
m_layerEntity.m_entity->GetId());
EXPECT_TRUE(layerDeleted);
m_layerEntity.m_entity = nullptr;
// Verify that the layer is gone.
EXPECT_EQ(looseEntities.size(), 0);
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Attempt to recover the empty layer, which should succeed.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
EXPECT_TRUE(recoveryResult.IsSuccess());
// Verify the layer was restored.
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerButLayerStillInScene_FailsToRestore)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
// Check that the layer is in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity,
layerEntityId);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Attempt to recover the layer, which should fail because it's still in the scene.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
EXPECT_FALSE(recoveryResult.IsSuccess());
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerWithEntityChild_RestoresCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
const AZ::EntityId childEntityId = childEntity->GetId();
AZ::TransformBus::Event(
childEntityId,
&AZ::TransformBus::Events::SetParent,
layerEntityId);
// Check that the layer and the child entity are in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 2);
bool foundLayer = false;
bool foundChild = false;
for (const auto& entity : looseEntities)
{
if (entity->GetId() == layerEntityId)
{
foundLayer = true;
}
else if (entity->GetId() == childEntityId)
{
foundChild = true;
}
}
EXPECT_TRUE(foundLayer);
EXPECT_TRUE(foundChild);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Delete the child from the scene.
bool childDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
childDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
childEntityId);
EXPECT_TRUE(childDeleted);
childEntity = nullptr;
// Delete the layer from the scene.
bool layerDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
layerDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
layerEntityId);
EXPECT_TRUE(layerDeleted);
m_layerEntity.m_entity = nullptr;
// Verify that the layer is gone.
EXPECT_EQ(looseEntities.size(), 0);
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Recover the layer.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
EXPECT_TRUE(recoveryResult.IsSuccess());
// Verify the layer and child were restored.
EXPECT_EQ(looseEntities.size(), 2);
foundLayer = false;
foundChild = false;
for (const auto& entity : looseEntities)
{
if (entity->GetId() == layerEntityId)
{
foundLayer = true;
}
else if (entity->GetId() == childEntityId)
{
foundChild = true;
}
}
EXPECT_TRUE(foundLayer);
EXPECT_TRUE(foundChild);
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerWithEntityChildStillInScene_FailsToRestore)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
const AZ::EntityId childEntityId = childEntity->GetId();
AZ::TransformBus::Event(
childEntityId,
&AZ::TransformBus::Events::SetParent,
layerEntityId);
// Check that the layer and the child entity are in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 2);
bool foundLayer = false;
bool foundChild = false;
for (const auto& entity : looseEntities)
{
if (entity->GetId() == layerEntityId)
{
foundLayer = true;
}
else if (entity->GetId() == childEntityId)
{
foundChild = true;
}
}
EXPECT_TRUE(foundLayer);
EXPECT_TRUE(foundChild);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Set the child's parent to invalid, so it sticks around after the layer is deleted
AZ::TransformBus::Event(
childEntityId,
&AZ::TransformBus::Events::SetParent,
AZ::EntityId());
// Delete the layer from the scene.
bool layerDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
layerDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
layerEntityId);
EXPECT_TRUE(layerDeleted);
m_layerEntity.m_entity = nullptr;
// Verify that the layer is gone, but the child is still in the scene.
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), childEntityId);
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Attempt to recover the layer, which should fail because the child is still in the scene.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
EXPECT_FALSE(recoveryResult.IsSuccess());
// Verify the layer was not restored.
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), childEntityId);
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerWithSliceInstance_RestoresCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
// First, set up a layer with a slice instance in it.
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Data::Asset<AZ::SliceAsset> loadedSliceAsset = instantiatedSlice.first->GetSliceAsset();
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
layerEntityId);
// Check that the layer is in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Clear out the scene, delete the instance and the layer.
DeleteSliceInstance(instantiatedSlice);
bool layerDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
layerDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
layerEntityId);
EXPECT_TRUE(layerDeleted);
m_layerEntity.m_entity = nullptr;
// Verify the slice instance and layer have been removed from the scene.
AZStd::list<AZ::SliceComponent::SliceReference>& sliceList = rootSlice->GetSlices();
EXPECT_EQ(sliceList.size(), 0);
// Verify the layer is gone.
EXPECT_EQ(looseEntities.size(), 0);
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Recover the layer. It has no parent, so use an invalid ID.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
EXPECT_TRUE(recoveryResult.IsSuccess());
// Verify the slice instance is now in the scene.
EXPECT_EQ(sliceList.size(), 1);
EXPECT_EQ(sliceList.front().GetSliceAsset(), loadedSliceAsset);
AZStd::unordered_set<AZ::SliceComponent::SliceInstance>& sliceInstances = sliceList.front().GetInstances();
EXPECT_EQ(sliceInstances.size(), 1);
// Verify the layer is now in the scene.
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
// Clean up the slice instance.
// There's only one instance, so no need to loop to clear it out.
// Convert the instance from an iterator to a pointer, to remove it.
rootSlice->RemoveSliceInstance(&(*sliceInstances.begin()));
}
TEST_F(EditorLayerComponentTest, LayerTests_RestoreLayerWithSliceInstanceStillInScene_FailsToRestore)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
// First, set up a layer with a slice instance in it.
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Data::Asset<AZ::SliceAsset> loadedSliceAsset = instantiatedSlice.first->GetSliceAsset();
AZ::Entity* childEntity = GetEntityFromSliceInstance(instantiatedSlice);
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
layerEntityId);
// Check that the layer is in the scene.
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 1);
EXPECT_EQ(looseEntities[0]->GetId(), layerEntityId);
// Next, save that layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = SaveMainLayer(layer, entitySaveStream, savedEntities, savedInstances);
EXPECT_TRUE(saveResult.IsSuccess());
// Move the slice instance out of the layer, so the entity ID remains active and conflicts with the layer.
AZ::TransformBus::Event(
childEntity->GetId(),
&AZ::TransformBus::Events::SetParent,
AZ::EntityId());
bool layerDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
layerDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
layerEntityId);
EXPECT_TRUE(layerDeleted);
m_layerEntity.m_entity = nullptr;
// Verify the layer is gone.
EXPECT_EQ(looseEntities.size(), 0);
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Attempt to recover the layer. It has no parent, so use an invalid ID.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
// Verify it failed to recover.
EXPECT_FALSE(recoveryResult.IsSuccess());
// Verify the slice instance is now in the scene.
AZStd::list<AZ::SliceComponent::SliceReference>& sliceList = rootSlice->GetSlices();
EXPECT_EQ(sliceList.size(), 1);
EXPECT_EQ(sliceList.front().GetSliceAsset(), loadedSliceAsset);
AZStd::unordered_set<AZ::SliceComponent::SliceInstance>& sliceInstances = sliceList.front().GetInstances();
EXPECT_EQ(sliceInstances.size(), 1);
// Verify the layer is not in the scene. If it failed, it shouldn't create the layer.
EXPECT_EQ(looseEntities.size(), 0);
// Clean up the slice instance.
DeleteSliceInstance(instantiatedSlice);
}
TEST_F(EditorLayerComponentTest, LayerTests_AttemptToCopyLayerComponent_IsNotCopyable)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
AZ::Entity::ComponentArrayType components;
components.push_back(m_layerEntity.m_layer);
bool isCopyable = EntityPropertyEditor::AreComponentsCopyable(components, DummyComponentFilter);
EXPECT_FALSE(isCopyable);
}
TEST_F(EditorLayerComponentTest, LayerTests_CheckOverwriteFlag_IsSetCorrectly)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
// Check layer created with correct value
EXPECT_FALSE(m_layerEntity.m_layer->GetOverwriteFlag());
//check direct call works correctly
m_layerEntity.m_layer->SetOverwriteFlag(true);
EXPECT_TRUE(m_layerEntity.m_layer->GetOverwriteFlag());
// check bus works correctly
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
m_layerEntity.m_entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::SetOverwriteFlag, true);
EXPECT_TRUE(m_layerEntity.m_layer->GetOverwriteFlag());
}
TEST_F(EditorLayerComponentTest, LayerTests_UndoRedoRestoreLayerWithChildren_AllRestoredEntitiesCorrect)
{
AUTO_RESULT_IF_SETTING_TRUE(UnitTest::prefabSystemSetting, true)
const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId();
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
EXPECT_NE(rootSlice, nullptr);
// Set up a an entity and a slice instance as children of the layer.
AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity");
const AZ::EntityId childEntityId = childEntity->GetId();
AZ::TransformBus::Event(
childEntityId,
&AZ::TransformBus::Events::SetParent,
layerEntityId);
AZ::SliceComponent::SliceInstanceAddress instantiatedSlice = CreateSliceInstance();
AZ::Data::Asset<AZ::SliceAsset> loadedSliceAsset = instantiatedSlice.first->GetSliceAsset();
AZ::Entity* sliceEntity = GetEntityFromSliceInstance(instantiatedSlice);
const AZ::EntityId sliceEntityId = sliceEntity->GetId();
AZ::TransformBus::Event(
sliceEntityId,
&AZ::TransformBus::Events::SetParent,
layerEntityId);
const EntityList& looseEntities(rootSlice->GetNewEntities());
EXPECT_EQ(looseEntities.size(), 2);
// Check that the layers, the entity, and the slice are in the scene.
EXPECT_TRUE(IsLooseEntityInRootSlice(rootSlice, layerEntityId));
EXPECT_TRUE(IsLooseEntityInRootSlice(rootSlice, childEntityId));
EXPECT_TRUE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
// Next, save the layer to a stream.
AZStd::vector<char> entitySaveBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > entitySaveStream(&entitySaveBuffer);
AZStd::vector<AZ::Entity*> savedEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs savedInstances;
Layers::EditorLayer layer;
Layers::LayerResult saveResult = m_layerEntity.m_layer->PopulateLayerWriteToStreamAndGetEntities(
savedEntities,
savedInstances,
entitySaveStream,
layer);
EXPECT_TRUE(saveResult.IsSuccess());
// Delete all the objects.
bool entityDeleted = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
entityDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
childEntityId);
EXPECT_TRUE(entityDeleted);
childEntity = nullptr;
DeleteSliceInstance(instantiatedSlice);
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
entityDeleted,
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
layerEntityId);
EXPECT_TRUE(entityDeleted);
m_layerEntity.m_entity = nullptr;
// Check that everything is gone.
EXPECT_EQ(looseEntities.size(), 0);
EXPECT_FALSE(IsInstanceAndReferenceInRootSlice(rootSlice, instantiatedSlice));
// Load the layer object from the stream it was saved to.
AZStd::shared_ptr<Layers::EditorLayer> loadedFromStream(AZ::Utils::LoadObjectFromStream<Layers::EditorLayer>(entitySaveStream));
EXPECT_NE(loadedFromStream, nullptr);
// Attempt to recover the layer. It has no parent, so use an invalid ID.
AZ::EntityId invalidParentId;
Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(loadedFromStream, "RecoveredLayerName", invalidParentId);
// Verify it recovered successfully.
EXPECT_TRUE(recoveryResult.IsSuccess());
// Verify the slice instance is now in the scene.
AZStd::list<AZ::SliceComponent::SliceReference>& sliceList = rootSlice->GetSlices();
EXPECT_EQ(sliceList.size(), 1);
EXPECT_EQ(sliceList.front().GetSliceAsset(), loadedSliceAsset);
AZStd::unordered_set<AZ::SliceComponent::SliceInstance>& sliceInstances = sliceList.front().GetInstances();
EXPECT_EQ(sliceInstances.size(), 1);
// Verify the loose entities are restored.
EXPECT_EQ(looseEntities.size(), 2);
// Check the hierarchy is correct.
AZ::EntityId childParentId;
AZ::TransformBus::EventResult(
childParentId,
childEntityId,
&AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(childParentId, layerEntityId);
AZ::EntityId sliceParentId;
AZ::TransformBus::EventResult(
sliceParentId,
sliceEntityId,
&AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(sliceParentId, layerEntityId);
// Undo.
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::UndoPressed);
// Check everything's gone again.
EXPECT_EQ(looseEntities.size(), 0);
EXPECT_EQ(sliceList.size(), 0);
// Redo.
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::RedoPressed);
// Check everything's back and the hierarchy is correct.
EXPECT_EQ(looseEntities.size(), 2);
EXPECT_TRUE(IsLooseEntityInRootSlice(rootSlice, layerEntityId));
EXPECT_TRUE(IsLooseEntityInRootSlice(rootSlice, childEntityId));
EXPECT_EQ(sliceList.size(), 1);
AZ::EntityId childParentIdAfterRedo;
AZ::TransformBus::EventResult(
childParentIdAfterRedo,
childEntityId,
&AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(childParentIdAfterRedo, layerEntityId);
AZ::EntityId sliceParentIdAfterRedo;
AZ::TransformBus::EventResult(
sliceParentIdAfterRedo,
sliceEntityId,
&AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(sliceParentIdAfterRedo, layerEntityId);
rootSlice->RemoveAllEntities();
}
}