/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(context)) { serializeContext->Class() ->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& entityList, AZ::SliceComponent::SliceReferenceToInstancePtrs& layerInstances, AZ::IO::ByteContainerStream >& 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& entityList, AZ::SliceComponent::SliceReferenceToInstancePtrs& layerInstances, AZ::IO::ByteContainerStream >& 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 >& layerStream, AZ::SliceComponent::SliceAssetToSliceInstancePtrs& sliceInstances, AZStd::unordered_map& uniqueEntities) { m_loadedLayer = AZ::Utils::LoadObjectFromStream(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& entityList, AZ::Entity* entity) { return AZStd::find(entityList.begin(), entityList.end(), entity) != entityList.end(); } bool IsEntityInLooseEditorEntities(AZ::Entity* entity) { AZStd::vector 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::ES_ACTIVE); createdEntity->Deactivate(); EXPECT_EQ(createdEntity->GetState(), AZ::Entity::ES_INIT); AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *createdEntity); createdEntity->Activate(); EXPECT_EQ(createdEntity->GetState(), AZ::Entity::ES_ACTIVE); return createdEntity; } EntityAndLayerComponent CreateEntityWithLayer(const char* entityName) { EntityAndLayerComponent result; result.m_entity = CreateEditorReadyEntity(entityName); result.m_layer = aznew EditorLayerComponentTestHelper(); AZStd::vector 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::ES_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(const AZ::SerializeContext::ClassData& classData) { return true; } class EditorLayerComponentTest : public ::testing::Test , public UnitTest::TraceBusRedirector { protected: void SetUp() override { m_app.Start(m_descriptor); // Without this, the user settings component would sometimes attempt to save // changes on shutdown. In some cases this would cause a crash while the unit test // was running, because the environment wasn't setup for it to save these settings. 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(); } AZStd::string GetLayerDirectory() const { return "Layers"; } // 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); return SaveMainLayer(layerOutput, entitySaveStream); } Layers::LayerResult SaveMainLayer( Layers::EditorLayer& layerOutput, AZ::IO::ByteContainerStream > &streamOutput, AZStd::vector& layerEntities, AZ::SliceComponent::SliceReferenceToInstancePtrs& instancesInLayers) { return m_layerEntity.m_layer->PopulateLayerWriteToStreamAndGetEntities( layerEntities, instancesInLayers, streamOutput, layerOutput); } Layers::LayerResult SaveMainLayer( Layers::EditorLayer& layerOutput, AZ::IO::ByteContainerStream > &streamOutput) { AZStd::vector layerEntities; AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers; return SaveMainLayer(layerOutput, streamOutput, layerEntities, instancesInLayers); } AZ::SliceComponent::SliceInstanceAddress CreateSliceInstanceFromSlice( AZ::SliceComponent* rootSlice, const AZ::Data::Asset& 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::ES_INIT); instanceEntity->Activate(); EXPECT_EQ(instanceEntity->GetState(), AZ::Entity::ES_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 sliceAsset; AZ::Entity* sliceEntity = aznew AZ::Entity("SliceEntity"); AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent(); 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(), true); sliceAsset.Create(assetId, false); sliceAsset.Get()->SetData(sliceEntity, sliceComponent, false); sliceComponent->Instantiate(); AZ::SliceComponent* rootSlice; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorRootSlice); EXPECT_NE(rootSlice, nullptr); rootSlice->RemoveSliceInstance(instance); } const char* m_entityName = "LayerEntityName"; AzToolsFramework::ToolsApplication m_app; 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(255), aznumeric_cast(128), aznumeric_cast(64), aznumeric_cast(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 layerEntities; layerEntities.insert(m_layerEntity.m_entity->GetId()); layerEntities.insert(secondLayer.m_entity->GetId()); AZStd::unordered_map 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 layerEntities; layerEntities.insert(m_layerEntity.m_entity->GetId()); layerEntities.insert(secondLayer.m_entity->GetId()); AZStd::unordered_map 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 layerEntities; layerEntities.insert(m_layerEntity.m_entity->GetId()); layerEntities.insert(secondLayer.m_entity->GetId()); AZStd::unordered_map 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 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 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. AZ::Vector3 scale(-1.0f,0.0f,0.0f); AZ::TransformBus::EventResult( scale, childEntity->GetId(), &AZ::TransformBus::Events::GetLocalScale); scale.SetX(scale.GetX() + 1.0f); AZ::TransformBus::Event( childEntity->GetId(), &AZ::TransformBus::Events::SetLocalScale, 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 layerEntities; AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers; AZStd::vector entitySaveBuffer; AZ::IO::ByteContainerStream > 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(64), aznumeric_cast(255), aznumeric_cast(128), aznumeric_cast(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(10), aznumeric_cast(30), aznumeric_cast(20), aznumeric_cast(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(6), aznumeric_cast(7), aznumeric_cast(8), aznumeric_cast(255)); m_layerEntity.m_layer->SetLayerColor(savedLayerColor); Layers::EditorLayer layer; AZStd::vector entitySaveBuffer; AZ::IO::ByteContainerStream > 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(20), aznumeric_cast(30), aznumeric_cast(40), aznumeric_cast(255)); m_layerEntity.m_layer->SetLayerColor(unsavedLayerColor); AZStd::unordered_map 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(getLayerColor.redF()), savedLayerColor.GetR()); EXPECT_FLOAT_EQ(aznumeric_cast(getLayerColor.greenF()), savedLayerColor.GetG()); EXPECT_FLOAT_EQ(aznumeric_cast(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 entitySaveBuffer; AZ::IO::ByteContainerStream > 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 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 entitySaveBuffer; AZ::IO::ByteContainerStream > 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 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 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) { AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity"); AZ::TransformBus::Event( childEntity->GetId(), &AZ::TransformBus::Events::SetParent, m_layerEntity.m_entity->GetId()); Layers::EditorLayer layer; AZStd::vector entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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) { 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 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) { 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 childLayerEntitySaveBuffer; AZ::IO::ByteContainerStream > childLayerEntitySaveStream(&childLayerEntitySaveBuffer); AZStd::vector 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 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 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) { 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) { 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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) { AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity"); AZ::TransformBus::Event( childEntity->GetId(), &AZ::TransformBus::Events::SetParent, m_layerEntity.m_entity->GetId()); Layers::EditorLayer layer; AZStd::vector entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 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) { AZ::Entity* childEntity = CreateEditorReadyEntity("ChildEntity"); AZ::TransformBus::Event( childEntity->GetId(), &AZ::TransformBus::Events::SetParent, m_layerEntity.m_entity->GetId()); Layers::EditorLayer layer; AZStd::vector entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 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) { AZ::EntityId invalidParentId; Layers::LayerResult recoveryResult = AzToolsFramework::Layers::EditorLayerComponent::RecoverEditorLayer(nullptr, "RecoveredLayerName", invalidParentId); EXPECT_FALSE(recoveryResult.IsSuccess()); } TEST_F(EditorLayerComponentTest, LayerTests_RestoreEmptyLayer_RestoresCorrectly) { AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId(); // Check that the layer is in the scene. AZ::SliceComponent* rootSlice; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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) { AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId(); // Check that the layer is in the scene. AZ::SliceComponent* rootSlice; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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) { 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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) { 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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) { 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 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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& 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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& 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) { 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 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::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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& sliceList = rootSlice->GetSlices(); EXPECT_EQ(sliceList.size(), 1); EXPECT_EQ(sliceList.front().GetSliceAsset(), loadedSliceAsset); AZStd::unordered_set& 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) { 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) { // 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) { const AZ::EntityId layerEntityId = m_layerEntity.m_entity->GetId(); AZ::SliceComponent* rootSlice; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( rootSlice, &AzToolsFramework::EditorEntityContextRequestBus::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 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 entitySaveBuffer; AZ::IO::ByteContainerStream > entitySaveStream(&entitySaveBuffer); AZStd::vector 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 loadedFromStream(AZ::Utils::LoadObjectFromStream(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& sliceList = rootSlice->GetSlices(); EXPECT_EQ(sliceList.size(), 1); EXPECT_EQ(sliceList.front().GetSliceAsset(), loadedSliceAsset); AZStd::unordered_set& 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(); } TEST_F(EditorLayerComponentTest, LayerTests_ModifyFileOnDiskAndWriteToLayerWithoutChangesInEditor_FileNotOverwritten) { QString testFolderPath = UnitTest::GetTestFolderPath().c_str(); // Get full file path of test layer file. AZStd::string layerFileFolerPath; AzFramework::StringFunc::Path::Join( UnitTest::GetTestFolderPath().c_str(), GetLayerDirectory().c_str(), layerFileFolerPath ); AZStd::string layerFullFileName( AZStd::string::format( "%s%s", m_entityName, AzToolsFramework::Layers::EditorLayerComponent::GetLayerExtensionWithDot().c_str() ) ); AZStd::string layerFullFilePath; AzFramework::StringFunc::Path::Join( layerFileFolerPath.c_str(), layerFullFileName.c_str(), layerFullFilePath ); // Get the test layer file and if it doesn't exist, create one. // The test file must exists before write layer to disk or WriteLayerAndGetEntities will fail. EXPECT_TRUE(QDir().mkpath(layerFileFolerPath.c_str())); QFile layerFile(layerFullFilePath.c_str()); layerFile.open(QIODevice::WriteOnly | QIODevice::Text); EXPECT_TRUE(layerFile.isOpen()); layerFile.close(); EXPECT_FALSE(layerFile.isOpen()); // Ckeck if the test layer file exists now. AZ::Outcome layerFullFilePathResult = m_layerEntity.m_layer->GetLayerFullFilePath(testFolderPath); EXPECT_TRUE(layerFullFilePathResult.IsSuccess()); // Write empty layer into test layer file. AZStd::vector layerEntities; AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers; Layers::LayerResult layerResult = m_layerEntity.m_layer->WriteLayerAndGetEntities( testFolderPath, layerEntities, instancesInLayers ); EXPECT_TRUE(layerResult.IsSuccess()); layerFile.open(QIODevice::ReadOnly | QIODevice::Text); EXPECT_TRUE(layerFile.isOpen()); QTextStream originalLayerInStream(&layerFile); QString originalLayerFileText = originalLayerInStream.readAll(); layerFile.close(); EXPECT_FALSE(layerFile.isOpen()); // Change test layer file on disk. QTextStream layerOutStream(&layerFile); layerFile.open(QIODevice::WriteOnly | QIODevice::Text); EXPECT_TRUE(layerFile.isOpen()); layerOutStream << "File Modified."; layerFile.close(); EXPECT_FALSE(layerFile.isOpen()); layerFile.open(QIODevice::ReadOnly | QIODevice::Text); EXPECT_TRUE(layerFile.isOpen()); QTextStream modifiedLayerInStream(&layerFile); QString modifiedLayerFileText = modifiedLayerInStream.readAll(); layerFile.close(); EXPECT_FALSE(layerFile.isOpen()); // Ensure the file gets updated. EXPECT_NE(modifiedLayerFileText, originalLayerFileText); // Try to save the unchanged empty layer. // The test layer file on disk should not get overwritten. layerResult = m_layerEntity.m_layer->WriteLayerAndGetEntities( testFolderPath, layerEntities, instancesInLayers ); EXPECT_TRUE(layerResult.IsSuccess()); layerFile.open(QIODevice::ReadOnly | QIODevice::Text); EXPECT_TRUE(layerFile.isOpen()); QTextStream currentLayerInStream(&layerFile); QString currentLayerFileText = currentLayerInStream.readAll(); layerFile.close(); EXPECT_FALSE(layerFile.isOpen()); // Ensure the file does not get overwritten. EXPECT_EQ(currentLayerFileText, modifiedLayerFileText); } }