From e16781e6b6b58b7d9db60b39de3152d8d5f2e9f6 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:24:49 -0800 Subject: [PATCH] LYN-8629 | Read-Only Entities - Setup (#6059) * Introduce read-only entity interface, handler and unit tests. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Switch from a push paradigm to a pull paradigm - handlers get to implement logic to determine if an entity should be read-only. This allows multiple systems to weigh into whether an entity is read-only. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Fixed to missing call in test Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Renaming ReadOnlyEntityQueryNotificationBus to ReadOnlyEntityQueryRequestBus for consistency with engine patterns. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../Application/ToolsApplication.cpp | 2 + .../AzToolsFrameworkModule.cpp | 2 + .../Entity/ReadOnly/ReadOnlyEntityBus.h | 63 +++++++++ .../Entity/ReadOnly/ReadOnlyEntityInterface.h | 43 ++++++ .../ReadOnlyEntitySystemComponent.cpp | 99 ++++++++++++++ .../ReadOnly/ReadOnlyEntitySystemComponent.h | 56 ++++++++ .../aztoolsframework_files.cmake | 4 + .../Entity/ReadOnly/ReadOnlyEntityFixture.cpp | 125 ++++++++++++++++++ .../Entity/ReadOnly/ReadOnlyEntityFixture.h | 78 +++++++++++ .../Entity/ReadOnly/ReadOnlyEntityTests.cpp | 99 ++++++++++++++ .../Tests/aztoolsframeworktests_files.cmake | 3 + 11 files changed, 574 insertions(+) create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h create mode 100644 Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.cpp create mode 100644 Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h create mode 100644 Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index afabbb233b..6c69fc04da 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -268,6 +269,7 @@ namespace AzToolsFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp index 2cdf409125..dd1ac12a02 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ namespace AzToolsFramework EditorEntityFixupComponent::CreateDescriptor(), EntityUtilityComponent::CreateDescriptor(), ContainerEntitySystemComponent::CreateDescriptor(), + ReadOnlyEntitySystemComponent::CreateDescriptor(), FocusModeSystemComponent::CreateDescriptor(), SliceMetadataEntityContextComponent::CreateDescriptor(), SliceRequestComponent::CreateDescriptor(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h new file mode 100644 index 0000000000..051e87c7e0 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + //! Used to notify changes of state for read-only entities. + class ReadOnlyEntityPublicNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when an entity's read-only status changes. + //! @param entityId The entity whose status has changed. + //! @param readOnly The read-only state the container was changed to. + virtual void OnReadOnlyEntityStatusChanged([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool readOnly) {} + + protected: + ~ReadOnlyEntityPublicNotifications() = default; + }; + using ReadOnlyEntityPublicNotificationBus = AZ::EBus; + + //! Used by the ReadOnlyEntitySystemComponent to query the read-only state of entities as set by systems using the API. + class ReadOnlyEntityQueryRequests + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when an entity's read-only status is queried. + //! Allows multiple systems to weigh in on the read-only status of an entity. + //! @param entityId The entity whose status has changed. + //! @param[out] isReadOnly The output of the query. Should only be changed to true, and left untouched if false. + virtual void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) = 0; + + protected: + ~ReadOnlyEntityQueryRequests() = default; + }; + using ReadOnlyEntityQueryRequestBus = AZ::EBus; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h new file mode 100644 index 0000000000..15ac04d0fd --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + //! An entity registered as read-only cannot be altered in the editor. + class ReadOnlyEntityPublicInterface + { + public: + AZ_RTTI(ReadOnlyEntityPublicInterface, "{921FE15B-6EBD-47F0-8238-BC63318DEDEA}"); + + //! Returns whether the entity id provided is registered as read-only. + virtual bool IsReadOnly(const AZ::EntityId& entityId) = 0; + }; + + //! An entity registered as read-only cannot be altered in the editor. + class ReadOnlyEntityQueryInterface + { + public: + AZ_RTTI(ReadOnlyEntityQueryInterface, "{2ACD63C5-1F3E-4DE8-880E-8115F857D329}"); + + //! Refreshes the cached read-only status for the entities provided. + //! @param entityIds The entityIds whose read-only state will be queried again. + virtual void RefreshReadOnlyState(const EntityIdList& entityIds) = 0; + + //! Refreshes the cached read-only status for all entities. + //! Useful when disconnecting a handler at runtime. + virtual void RefreshReadOnlyStateForAllEntities() = 0; + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp new file mode 100644 index 0000000000..f7f36177c9 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include + +namespace AzToolsFramework +{ + void ReadOnlyEntitySystemComponent::Activate() + { + AZ::Interface::Register(this); + AZ::Interface::Register(this); + EditorEntityContextNotificationBus::Handler::BusConnect(); + } + + void ReadOnlyEntitySystemComponent::Deactivate() + { + EditorEntityContextNotificationBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); + AZ::Interface::Unregister(this); + } + + void ReadOnlyEntitySystemComponent::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } + } + + void ReadOnlyEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("ReadOnlyEntityService")); + } + + bool ReadOnlyEntitySystemComponent::IsReadOnly(const AZ::EntityId& entityId) + { + if (!m_readOnlystates.contains(entityId)) + { + QueryReadOnlyStateForEntity(entityId); + } + + return m_readOnlystates[entityId]; + } + + void ReadOnlyEntitySystemComponent::RefreshReadOnlyState(const EntityIdList& entityIds) + { + for (const AZ::EntityId entityId : entityIds) + { + bool wasReadOnly = m_readOnlystates[entityId]; + QueryReadOnlyStateForEntity(entityId); + + if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly) + { + ReadOnlyEntityPublicNotificationBus::Broadcast( + &ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly); + } + } + } + + void ReadOnlyEntitySystemComponent::RefreshReadOnlyStateForAllEntities() + { + for (auto elem : m_readOnlystates) + { + AZ::EntityId entityId = elem.first; + bool wasReadOnly = m_readOnlystates[entityId]; + QueryReadOnlyStateForEntity(entityId); + + if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly) + { + ReadOnlyEntityPublicNotificationBus::Broadcast( + &ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly); + } + } + } + + void ReadOnlyEntitySystemComponent::OnContextReset() + { + m_readOnlystates.clear(); + } + + void ReadOnlyEntitySystemComponent::QueryReadOnlyStateForEntity(const AZ::EntityId& entityId) + { + bool isReadOnly = false; + + ReadOnlyEntityQueryRequestBus::Broadcast( + &ReadOnlyEntityQueryRequestBus::Events::IsReadOnly, entityId, isReadOnly); + + m_readOnlystates[entityId] = isReadOnly; + } + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h new file mode 100644 index 0000000000..efc91e9d89 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include +#include + +namespace AzToolsFramework +{ + //! System Component to track read-only entity registration. + //! An entity registered as ReadOnly cannot be altered in the Editor. + class ReadOnlyEntitySystemComponent final + : public AZ::Component + , private ReadOnlyEntityPublicInterface + , private ReadOnlyEntityQueryInterface + , private EditorEntityContextNotificationBus::Handler + { + public: + AZ_COMPONENT(ReadOnlyEntitySystemComponent, "{B32EB03F-D88F-4B3A-9C16-071AF04DA646}"); + + ReadOnlyEntitySystemComponent() = default; + virtual ~ReadOnlyEntitySystemComponent() = default; + + // AZ::Component overrides ... + void Activate() override; + void Deactivate() override; + + static void Reflect(AZ::ReflectContext* context); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + + // ReadOnlyEntityPublicNotifications overrides ... + bool IsReadOnly(const AZ::EntityId& entityId) override; + + // ReadOnlyEntityQueryInterface overrides ... + void RefreshReadOnlyState(const EntityIdList& entityIds) override; + void RefreshReadOnlyStateForAllEntities() override; + + // EditorEntityContextNotificationBus overrides ... + void OnContextReset() override; + + private: + void QueryReadOnlyStateForEntity(const AZ::EntityId& entityId); + + AZStd::unordered_map m_readOnlystates; + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index bdf6abe141..a6c9adb19e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -159,6 +159,10 @@ set(FILES Entity/SliceEditorEntityOwnershipServiceBus.h Entity/EntityUtilityComponent.h Entity/EntityUtilityComponent.cpp + Entity/ReadOnly/ReadOnlyEntityInterface.h + Entity/ReadOnly/ReadOnlyEntityBus.h + Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp + Entity/ReadOnly/ReadOnlyEntitySystemComponent.h Fingerprinting/TypeFingerprinter.h Fingerprinting/TypeFingerprinter.cpp FocusMode/FocusModeInterface.h diff --git a/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.cpp b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.cpp new file mode 100644 index 0000000000..d80ecebce0 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include + +namespace AzToolsFramework +{ + void ReadOnlyEntityFixture::SetUpEditorFixtureImpl() + { + // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is + // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash + // in the unit tests. + AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); + + m_readOnlyEntityPublicInterface = AZ::Interface::Get(); + ASSERT_TRUE(m_readOnlyEntityPublicInterface != nullptr); + + GenerateTestHierarchy(); + } + + void ReadOnlyEntityFixture::TearDownEditorFixtureImpl() + { + } + + void ReadOnlyEntityFixture::GenerateTestHierarchy() + { + /* + * Root + * |_ Child + * |_ GrandChild1 + * |_ GrandChild2 + */ + + m_entityMap[RootEntityName] = CreateEditorEntity(RootEntityName, AZ::EntityId()); + m_entityMap[ChildEntityName] = CreateEditorEntity(ChildEntityName, m_entityMap[RootEntityName]); + m_entityMap[GrandChild1EntityName] = CreateEditorEntity(GrandChild1EntityName, m_entityMap[ChildEntityName]); + m_entityMap[GrandChild2EntityName] = CreateEditorEntity(GrandChild2EntityName, m_entityMap[ChildEntityName]); + } + + AZ::EntityId ReadOnlyEntityFixture::CreateEditorEntity(const char* name, AZ::EntityId parentId) + { + AZ::Entity* entity = nullptr; + UnitTest::CreateDefaultEditorEntity(name, &entity); + + // Parent + AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId); + + return entity->GetId(); + } + + ReadOnlyHandlerAlwaysTrue::ReadOnlyHandlerAlwaysTrue() + { + auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + + ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId); + } + + ReadOnlyHandlerAlwaysTrue::~ReadOnlyHandlerAlwaysTrue() + { + ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect(); + + if (auto readOnlyEntityQueryInterface = AZ::Interface::Get()) + { + readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities(); + } + } + + void ReadOnlyHandlerAlwaysTrue::IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, bool& isReadOnly) + { + isReadOnly = true; + } + + ReadOnlyHandlerAlwaysFalse::ReadOnlyHandlerAlwaysFalse() + { + auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + + ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId); + } + + ReadOnlyHandlerAlwaysFalse::~ReadOnlyHandlerAlwaysFalse() + { + ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect(); + + if (auto readOnlyEntityQueryInterface = AZ::Interface::Get()) + { + readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities(); + } + } + + ReadOnlyHandlerEntityId::ReadOnlyHandlerEntityId(AZ::EntityId entityId) + : m_entityId(entityId) + { + auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + + ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId); + } + + ReadOnlyHandlerEntityId::~ReadOnlyHandlerEntityId() + { + ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect(); + + if (auto readOnlyEntityQueryInterface = AZ::Interface::Get()) + { + readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities(); + } + } + + void ReadOnlyHandlerEntityId::IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) + { + if (entityId == m_entityId) + { + isReadOnly = true; + } + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h new file mode 100644 index 0000000000..72fff56c6a --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace AzToolsFramework +{ + class ReadOnlyEntityFixture + : public UnitTest::ToolsApplicationFixture + { + protected: + void SetUpEditorFixtureImpl() override; + void TearDownEditorFixtureImpl() override; + + void GenerateTestHierarchy(); + AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId); + + AZStd::unordered_map m_entityMap; + + ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr; + + public: + inline static const char* RootEntityName = "Root"; + inline static const char* ChildEntityName = "Child"; + inline static const char* GrandChild1EntityName = "GrandChild1"; + inline static const char* GrandChild2EntityName = "GrandChild2"; + }; + + class ReadOnlyHandlerAlwaysTrue + : public ReadOnlyEntityQueryRequestBus::Handler + { + public: + ReadOnlyHandlerAlwaysTrue(); + ~ReadOnlyHandlerAlwaysTrue(); + + // ReadOnlyEntityQueryNotificationBus overrides ... + void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override; + }; + + class ReadOnlyHandlerAlwaysFalse + : public ReadOnlyEntityQueryRequestBus::Handler + { + public: + ReadOnlyHandlerAlwaysFalse(); + ~ReadOnlyHandlerAlwaysFalse(); + + // ReadOnlyEntityQueryNotificationBus overrides ... + void IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool& isReadOnly) override {} + }; + + class ReadOnlyHandlerEntityId + : public ReadOnlyEntityQueryRequestBus::Handler + { + public: + ReadOnlyHandlerEntityId(AZ::EntityId entityId); + ~ReadOnlyHandlerEntityId(); + + // ReadOnlyEntityQueryNotificationBus overrides ... + void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override; + + private: + AZ::EntityId m_entityId; + }; +} diff --git a/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp new file mode 100644 index 0000000000..532325e208 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Entity/ReadOnly/ReadOnlyEntityTests.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace AzToolsFramework +{ + TEST_F(ReadOnlyEntityFixture, NoHandlerEntityIsNotReadOnlyByDefault) + { + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + } + + TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsReadOnly) + { + // Create a handler that sets all entities to read-only. + ReadOnlyHandlerAlwaysTrue alwaysTrueHandler; + + // All entities should be marked read-only now. + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName])); + } + + TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsNotReadOnly) + { + // Create a handler that sets all entities to read-only. + ReadOnlyHandlerAlwaysFalse alwaysFalseHandler; + + // All entities should not be marked read-only now. + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName])); + } + + TEST_F(ReadOnlyEntityFixture, SingleHandlerWithLogic) + { + // Create a handler that sets just the child entity to read-only. + ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]); + + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName])); + } + + TEST_F(ReadOnlyEntityFixture, TwoHandlersCanOverlap) + { + // Create two handlers that set different entities to read-only. + ReadOnlyHandlerEntityId entityIdHandler1(m_entityMap[ChildEntityName]); + ReadOnlyHandlerEntityId entityIdHandler2(m_entityMap[GrandChild2EntityName]); + + // Both entities should be marked as read-only, while others aren't. + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName])); + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName])); + } + + TEST_F(ReadOnlyEntityFixture, EnsureCacheIsRefreshedCorrectly) + { + // Verify the child entity is not marked as read-only + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + + // Create a handler that sets the child entity to read-only. + ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]); + + // Communicate to the ReadOnlyEntitySystemComponent that the read-only state for the child entity may have changed. + // Note that this operation would usually be executed by the handler, hence the Query interface call. + if (auto readOnlyEntityQueryInterface = AZ::Interface::Get()) + { + readOnlyEntityQueryInterface->RefreshReadOnlyState({ m_entityMap[ChildEntityName] }); + } + + // Verify the child entity is marked as read-only + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + } + + TEST_F(ReadOnlyEntityFixture, EnsureCacheIsClearedCorrectly) + { + { + // Create a handler that sets the child entity to read-only. + ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]); + + // Verify the child entity is marked as read-only + EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + } + // When the handler goes out of scope, it calls RefreshReadOnlyStateForAllEntities and refreshes the cache. + + // Verify the child entity is no longer marked as read-only + EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName])); + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 8e0297fb1f..b2b50ca7c6 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -28,6 +28,9 @@ set(FILES Entity/EditorEntitySearchComponentTests.cpp Entity/EditorEntitySelectionTests.cpp Entity/EntityUtilityComponentTests.cpp + Entity/ReadOnly/ReadOnlyEntityFixture.cpp + Entity/ReadOnly/ReadOnlyEntityFixture.h + Entity/ReadOnly/ReadOnlyEntityTests.cpp EntityIdQLabelTests.cpp EntityInspectorTests.cpp EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp