diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py index 3403c938a8..6cc48984ab 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py @@ -253,73 +253,3 @@ class TestAtomEditorComponentsMain(object): ) -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_generic']) -@pytest.mark.system -class TestMaterialEditorBasicTests(object): - @pytest.fixture(autouse=True) - def setup_teardown(self, request, workspace, project): - def delete_files(): - file_system.delete( - [ - os.path.join(workspace.paths.project(), "Materials", "test_material.material"), - os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"), - os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"), - ], - True, - True, - ) - # Cleanup our newly created materials - delete_files() - - def teardown(): - # Cleanup our newly created materials - delete_files() - - request.addfinalizer(teardown) - - @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) - @pytest.mark.test_case_id("C34448113") # Creating a New Asset. - @pytest.mark.test_case_id("C34448114") # Opening an Existing Asset. - @pytest.mark.test_case_id("C34448115") # Closing Selected Material. - @pytest.mark.test_case_id("C34448116") # Closing All Materials. - @pytest.mark.test_case_id("C34448117") # Closing all but Selected Material. - @pytest.mark.test_case_id("C34448118") # Saving Material. - @pytest.mark.test_case_id("C34448119") # Saving as a New Material. - @pytest.mark.test_case_id("C34448120") # Saving as a Child Material. - @pytest.mark.test_case_id("C34448121") # Saving all Open Materials. - def test_MaterialEditorBasicTests( - self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name): - - expected_lines = [ - "Material opened: True", - "Test asset doesn't exist initially: True", - "New asset created: True", - "New Material opened: True", - "Material closed: True", - "All documents closed: True", - "Close All Except Selected worked as expected: True", - "Actual Document saved with changes: True", - "Document saved as copy is saved with changes: True", - "Document saved as child is saved with changes: True", - "Save All worked as expected: True", - ] - unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", - "Traceback (most recent call last):" - ] - - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - generic_launcher, - "hydra_AtomMaterialEditor_BasicTests.py", - run_python="--runpython", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - log_file_name="MaterialEditor.log", - ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index 58e5d00ff2..9bb7f9c50e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -70,3 +70,75 @@ class TestAtomEditorComponentsSandbox(object): null_renderer=True, cfg_args=cfg_args, ) + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_generic']) +@pytest.mark.system +class TestMaterialEditorBasicTests(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request, workspace, project): + def delete_files(): + file_system.delete( + [ + os.path.join(workspace.paths.project(), "Materials", "test_material.material"), + os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"), + os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"), + ], + True, + True, + ) + # Cleanup our newly created materials + delete_files() + + def teardown(): + # Cleanup our newly created materials + delete_files() + + request.addfinalizer(teardown) + + @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) + @pytest.mark.test_case_id("C34448113") # Creating a New Asset. + @pytest.mark.test_case_id("C34448114") # Opening an Existing Asset. + @pytest.mark.test_case_id("C34448115") # Closing Selected Material. + @pytest.mark.test_case_id("C34448116") # Closing All Materials. + @pytest.mark.test_case_id("C34448117") # Closing all but Selected Material. + @pytest.mark.test_case_id("C34448118") # Saving Material. + @pytest.mark.test_case_id("C34448119") # Saving as a New Material. + @pytest.mark.test_case_id("C34448120") # Saving as a Child Material. + @pytest.mark.test_case_id("C34448121") # Saving all Open Materials. + def test_MaterialEditorBasicTests( + self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name): + + expected_lines = [ + "Material opened: True", + "Test asset doesn't exist initially: True", + "New asset created: True", + "New Material opened: True", + "Material closed: True", + "All documents closed: True", + "Close All Except Selected worked as expected: True", + "Actual Document saved with changes: True", + "Document saved as copy is saved with changes: True", + "Document saved as child is saved with changes: True", + "Save All worked as expected: True", + ] + unexpected_lines = [ + # "Trace::Assert", + # "Trace::Error", + "Traceback (most recent call last):" + ] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + generic_launcher, + "hydra_AtomMaterialEditor_BasicTests.py", + run_python="--runpython", + timeout=120, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + null_renderer=True, + log_file_name="MaterialEditor.log", + ) + diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h index 22b65f8340..c657e0edc7 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -43,7 +43,7 @@ namespace AzFramework class IMatchmakingAsyncRequests { public: - AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); + AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default; @@ -60,4 +60,31 @@ namespace AzFramework // @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; + + //! MatchmakingAsyncRequestNotifications + //! The notifications correspond to matchmaking async requests + class MatchmakingAsyncRequestNotifications + : public AZ::EBusTraits + { + public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + virtual void OnAcceptMatchAsyncComplete() = 0; + + // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + // @param matchmakingTicketId The unique identifier for the matchmaking ticket + virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; + + // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + virtual void OnStopMatchmakingAsyncComplete() = 0; + }; + using MatchmakingAsyncRequestNotificationBus = AZ::EBus; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h index ad61971a11..aa19b94b4a 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -13,36 +13,10 @@ namespace AzFramework { - //! MatchmakingAsyncRequestNotifications - //! The notifications correspond to matchmaking async requests - class MatchmakingAsyncRequestNotifications - : public AZ::EBusTraits - { - public: - // Safeguard handler for multi-threaded use case - using MutexType = AZStd::recursive_mutex; - - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - ////////////////////////////////////////////////////////////////////////// - - // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes - virtual void OnAcceptMatchAsyncComplete() = 0; - - // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes - // @param matchmakingTicketId The unique identifier for the matchmaking ticket - virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; - - // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes - virtual void OnStopMatchmakingAsyncComplete() = 0; - }; - using MatchmakingAsyncRequestNotificationBus = AZ::EBus; - //! MatchmakingNotifications //! The matchmaking notifications to listen for performing required operations - class MatchAcceptanceNotifications + //! based on matchmaking ticket event + class MatchmakingNotifications : public AZ::EBusTraits { public: @@ -55,8 +29,18 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE + // OnMatchAcceptance is fired when match is found and pending on acceptance + // Use this notification to accept found match virtual void OnMatchAcceptance() = 0; + + // OnMatchComplete is fired when match is complete + virtual void OnMatchComplete() = 0; + + // OnMatchError is fired when match is processed with error + virtual void OnMatchError() = 0; + + // OnMatchFailure is fired when match is failed to complete + virtual void OnMatchFailure() = 0; }; - using MatchAcceptanceNotificationBus = AZ::EBus; + using MatchmakingNotificationBus = AZ::EBus; } // namespace AzFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 7db507e751..67b5d99011 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -53,7 +53,7 @@ namespace AzToolsFramework AZ_Assert(m_loaderInterface != nullptr, "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work"); - m_rootInstance = AZStd::unique_ptr(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab")); + m_rootInstance = AZStd::unique_ptr(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab")); m_sliceOwnershipService.BusConnect(m_entityContextId); m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage; m_editorSliceOwnershipService.BusConnect(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index a79b9eb73d..09b5745a90 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab "Instance Entity Mapper Interface could not be found. " "Check that it is being correctly initialized."); + EditorEntityInfoNotificationBus::Handler::BusConnect(); EditorEntityContextNotificationBus::Handler::BusConnect(); + PrefabPublicNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); AZ::Interface::Register(this); } @@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab { AZ::Interface::Unregister(this); AZ::Interface::Unregister(this); + PrefabPublicNotificationBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect(); + EditorEntityInfoNotificationBus::Handler::BusDisconnect(); } - void PrefabFocusHandler::Initialize() + void PrefabFocusHandler::InitializeEditorInterfaces() { m_containerEntityInterface = AZ::Interface::Get(); AZ_Assert( @@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab "Prefab - PrefabFocusHandler - " "Focus Mode Interface could not be found. " "Check that it is being correctly initialized."); - - m_instanceEntityMapperInterface = AZ::Interface::Get(); - AZ_Assert( - m_instanceEntityMapperInterface, - "Prefab - PrefabFocusHandler - " - "Instance Entity Mapper Interface could not be found. " - "Check that it is being correctly initialized."); } PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) @@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) { - if (index < 0 || index >= m_instanceFocusVector.size()) + if (index < 0 || index >= m_instanceFocusHierarchy.size()) { return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); } - InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; + InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index]; FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId()); @@ -134,41 +131,37 @@ namespace AzToolsFramework::Prefab return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); } - if (!m_isInitialized) - { - Initialize(); - } - - if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get()) - { - // Close all container entities in the old path - CloseInstanceContainers(m_instanceFocusVector); + // Close all container entities in the old path. + CloseInstanceContainers(m_instanceFocusHierarchy); - m_focusedInstance = focusedInstance; - m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + m_focusedInstance = focusedInstance; + m_focusedTemplateId = focusedInstance->get().GetTemplateId(); - AZ::EntityId containerEntityId; + AZ::EntityId containerEntityId; - if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) - { - containerEntityId = focusedInstance->get().GetContainerEntityId(); - } - else - { - containerEntityId = AZ::EntityId(); - } + if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) + { + containerEntityId = focusedInstance->get().GetContainerEntityId(); + } + else + { + containerEntityId = AZ::EntityId(); + } - // Focus on the descendants of the container entity + // Focus on the descendants of the container entity in the Editor, if the interface is initialized. + if (m_focusModeInterface) + { m_focusModeInterface->SetFocusRoot(containerEntityId); + } - // Refresh path variables - RefreshInstanceFocusList(); + // Refresh path variables. + RefreshInstanceFocusList(); + RefreshInstanceFocusPath(); - // Open all container entities in the new path - OpenInstanceContainers(m_instanceFocusVector); + // Open all container entities in the new path. + OpenInstanceContainers(m_instanceFocusHierarchy); - PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); - } + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); return AZ::Success(); } @@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { - return aznumeric_cast(m_instanceFocusVector.size()); + return aznumeric_cast(m_instanceFocusHierarchy.size()); } - void PrefabFocusHandler::OnEntityStreamLoadSuccess() + void PrefabFocusHandler::OnContextReset() { - if (!m_isInitialized) - { - Initialize(); - } - // Clear the old focus vector - m_instanceFocusVector.clear(); + m_instanceFocusHierarchy.clear(); // Focus on the root prefab (AZ::EntityId() will default to it) FocusOnPrefabInstanceOwningEntityId(AZ::EntityId()); } + void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name) + { + // Determine if the entityId is the container for any of the instances in the vector + auto result = AZStd::find_if( + m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(), + [entityId](const InstanceOptionalReference& instance) + { + return (instance->get().GetContainerEntityId() == entityId); + } + ); + + if (result != m_instanceFocusHierarchy.end()) + { + // Refresh the path and notify changes. + RefreshInstanceFocusPath(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); + } + } + + void PrefabFocusHandler::OnPrefabInstancePropagationEnd() + { + // Refresh the path and notify changes in case propagation updated any container names. + RefreshInstanceFocusPath(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); + } + void PrefabFocusHandler::RefreshInstanceFocusList() { - m_instanceFocusVector.clear(); - m_instanceFocusPath.clear(); + m_instanceFocusHierarchy.clear(); AZStd::list instanceFocusList; - // Use a support list to easily push front while traversing the prefab hierarchy InstanceOptionalReference currentInstance = m_focusedInstance; while (currentInstance.has_value()) { - instanceFocusList.push_front(currentInstance); + m_instanceFocusHierarchy.emplace_back(currentInstance); currentInstance = currentInstance->get().GetParentInstance(); } - // Populate internals using the support list - for (auto& instance : instanceFocusList) + // Invert the vector, since we need the top instance to be at index 0 + AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end()); + } + + void PrefabFocusHandler::RefreshInstanceFocusPath() + { + m_instanceFocusPath.clear(); + + for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy) { m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); - m_instanceFocusVector.emplace_back(instance); } } void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector& instances) const { + // If this is called outside the Editor, this interface won't be initialized. + if (!m_containerEntityInterface) + { + return; + } + for (const InstanceOptionalReference& instance : instances) { if (instance.has_value()) @@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector& instances) const { + // If this is called outside the Editor, this interface won't be initialized. + if (!m_containerEntityInterface) + { + return; + } + for (const InstanceOptionalReference& instance : instances) { if (instance.has_value()) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 80b7a6859c..6a71365d8e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -11,9 +11,11 @@ #include #include +#include #include #include #include +#include #include namespace AzToolsFramework @@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab class PrefabFocusHandler final : private PrefabFocusInterface , private PrefabFocusPublicInterface + , private PrefabPublicNotificationBus::Handler , private EditorEntityContextNotificationBus::Handler + , private EditorEntityInfoNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); @@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab PrefabFocusHandler(); ~PrefabFocusHandler(); - void Initialize(); - // PrefabFocusInterface overrides ... + void InitializeEditorInterfaces() override; PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override; TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; @@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; // EditorEntityContextNotificationBus overrides ... - void OnEntityStreamLoadSuccess() override; + void OnContextReset() override; + + // EditorEntityInfoNotificationBus overrides ... + void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override; + + // PrefabPublicNotifications overrides ... + void OnPrefabInstancePropagationEnd(); private: PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); void RefreshInstanceFocusList(); + void RefreshInstanceFocusPath(); void OpenInstanceContainers(const AZStd::vector& instances) const; void CloseInstanceContainers(const AZStd::vector& instances) const; + //! The instance the editor is currently focusing on. InstanceOptionalReference m_focusedInstance; + //! The templateId of the focused instance. TemplateId m_focusedTemplateId; - AZStd::vector m_instanceFocusVector; + //! The list of instances going from the root (index 0) to the focused instance. + AZStd::vector m_instanceFocusHierarchy; + //! A path containing the names of the containers in the instance focus hierarchy, separated with a /. AZ::IO::Path m_instanceFocusPath; ContainerEntityInterface* m_containerEntityInterface = nullptr; FocusModeInterface* m_focusModeInterface = nullptr; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; - - bool m_isInitialized = false; }; } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h index 25c83b89bc..287cbbaf96 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h @@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab public: AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}"); + //! Initializes the editor interfaces for Prefab Focus mode. + //! If this is not called on initialization, the Prefab Focus Mode functions will still work + //! but won't trigger the Editor APIs to visualize focus mode on the UI. + virtual void InitializeEditorInterfaces() = 0; + //! Set the focused prefab instance to the owning instance of the entityId provided. //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 526912cf29..bef08466ae 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,10 @@ namespace AzToolsFramework return; } + // Initialize Editor functionality for the Prefab Focus Handler + auto prefabFocusInterface = AZ::Interface::Get(); + prefabFocusInterface->InitializeEditorInterfaces(); + EditorContextMenuBus::Handler::BusConnect(); EditorEventsBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect(); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp index 63536a160b..516f6beb3c 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -98,10 +100,26 @@ namespace AssetProcessorMessagesTests int argC = 0; m_batchApplicationManager = AZStd::make_unique(&argC, nullptr, nullptr); - m_batchApplicationManager->BeforeRun(); - // Override Game Name to be "AutomatedTesting" - AssetUtilities::ComputeProjectName("AutomatedTesting", true); + auto registry = AZ::SettingsRegistry::Get(); + EXPECT_NE(registry, nullptr); + constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{ + AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey + }; + constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" }; + registry->Set(projectPathKey, "AutomatedTesting"); + AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); + + // Force the branch token into settings registry before starting the application manager. + // This avoids writing the asset_processor.setreg file which can cause fileIO errors. + const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath(); + constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" }; + AZStd::string token; + AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token); + registry->Set(branchTokenKey, token.c_str()); + + auto status = m_batchApplicationManager->BeforeRun(); + ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success); m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration(); m_batchApplicationManager->InitAssetProcessorManager(); @@ -159,21 +177,25 @@ namespace AssetProcessorMessagesTests ASSERT_TRUE(result); }); - - } void TearDown() override { - QEventLoop eventLoop; + if (m_batchApplicationManager->m_connectionManager) + { + QEventLoop eventLoop; - QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit); + QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit); - m_batchApplicationManager->m_connectionManager->QuitRequested(); + m_batchApplicationManager->m_connectionManager->QuitRequested(); - eventLoop.exec(); + eventLoop.exec(); + } - m_assetSystemComponent->Deactivate(); + if (m_assetSystemComponent) + { + m_assetSystemComponent->Deactivate(); + } m_batchApplicationManager->Destroy(); } diff --git a/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp b/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp index c237f4801e..a024ce6c7a 100644 --- a/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/ApplicationManager.cpp @@ -505,6 +505,14 @@ bool ApplicationManager::StartAZFramework() AzFramework::Application::Descriptor appDescriptor; AZ::ComponentApplication::StartupParameters params; + QDir projectPath{ AssetUtilities::ComputeProjectPath() }; + if (!projectPath.exists("project.json")) + { + AZStd::string errorMsg = AZStd::string::format("Path '%s' is not a valid project path.", projectPath.path().toUtf8().constData()); + AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::OnErrorMessage, errorMsg.c_str()); + return false; + } + QString projectName = AssetUtilities::ComputeProjectName(); // Prevent loading of gems in the Create method of the ComponentApplication @@ -520,7 +528,6 @@ bool ApplicationManager::StartAZFramework() //Registering all the Components m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor()); - Reflect(); const AzFramework::CommandLine* commandLine = nullptr; diff --git a/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp b/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp index 40d3bd3caa..c3ff22a39e 100644 --- a/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp @@ -95,6 +95,8 @@ GUIApplicationManager::~GUIApplicationManager() ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun() { + AssetProcessor::MessageInfoBus::Handler::BusConnect(); + ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun(); if (status != ApplicationManager::BeforeRunStatus::Status_Success) { @@ -109,7 +111,6 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun() #if defined(EXTERNAL_CRASH_REPORTING) CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString()); #endif - AssetProcessor::MessageInfoBus::Handler::BusConnect(); // we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted // It is important to note that we are monitoring the parent folder and not the actual cache folder itself since @@ -436,98 +437,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message) connection = Qt::QueuedConnection; } - if (m_isCurrentlyLoadingGems) - { - // if something goes wrong during gem initialization, this is a special case and we need to be extra helpful. - const char* userSettingsFile = "_WAF_/user_settings.options"; - const char* defaultSettingsFile = "_WAF_/default_settings.json"; - - QDir engineRoot; - AssetUtilities::ComputeEngineRoot(engineRoot); - - QString settingsPath = engineRoot.absoluteFilePath(userSettingsFile); - QString friendlyErrorMessage; - bool usingDefaults = false; - - if (QFile::exists(settingsPath)) - { - QSettings loader(settingsPath, QSettings::IniFormat); - QVariant settingValue = loader.value("Game Projects/enabled_game_projects"); - QStringList compiledProjects = settingValue.toStringList(); - - if (compiledProjects.isEmpty()) - { - QByteArray byteArray; - QFile jsonFile; - jsonFile.setFileName(engineRoot.absoluteFilePath(defaultSettingsFile)); - jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); - byteArray = jsonFile.readAll(); - jsonFile.close(); - - QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object(); - QJsonArray projectsArray = settingsObject["Game Projects"].toArray(); - - if (!projectsArray.isEmpty()) - { - auto projectObject = projectsArray[0].toObject(); - QString projects = projectObject["default_value"].toString(); - - if (!projects.isEmpty()) - { - compiledProjects = projects.split(','); - usingDefaults = true; - } - } - } - - for (int i = 0; i < compiledProjects.size(); ++i) - { - compiledProjects[i] = compiledProjects[i].trimmed(); - } - - QString enabledProject = AssetUtilities::ComputeProjectName(); - - if (!compiledProjects.contains(enabledProject)) - { - QString projectSourceLine; - - if (usingDefaults) - { - projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile); - } - else - { - projectSourceLine = QString("The currently compiled projects according to %1 are '%2'").arg(userSettingsFile); - } - - projectSourceLine = projectSourceLine.arg(compiledProjects.join(", ")); - friendlyErrorMessage = QString("An error occurred while loading gems.\n" - "The enabled game project is not in the list of compiled projects.\n" - "Please configure the enabled project to be compiled and rebuild or change the enabled project.\n" - "The currently enabled game project (from bootstrap.cfg or /%4 command-line parameter) is '%1'.\n" - "%2\n" - "Full error text:\n" - "%3" - ).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter); - } - } - - if (friendlyErrorMessage.isEmpty()) - { - friendlyErrorMessage = QString("An error occurred while loading gems.\n" - "This can happen when new gems are added to a project, but those gems need to be built in order to function.\n" - "This can also happen when switching to a different project, one which uses gems which are not yet built.\n" - "To continue, please build the current project before attempting to run Asset Processor again.\n\n" - "Full error text:\n" - "%1").arg(message); - } - QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, friendlyErrorMessage), Q_ARG(bool, true)); - } - else - { - QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true)); - } - + QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true)); return true; } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h similarity index 53% rename from Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h rename to Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h index c14ef559b2..8ab215d741 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftMatchmakingRequestBus.h @@ -5,75 +5,16 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #pragma once #include -#include #include #include -#include namespace AWSGameLift { - //! IAWSGameLiftRequests - //! GameLift Gem interfaces to configure client manager - class IAWSGameLiftRequests - { - public: - AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}"); - - IAWSGameLiftRequests() = default; - virtual ~IAWSGameLiftRequests() = default; - - //! ConfigureGameLiftClient - //! Configure GameLift client to interact with Amazon GameLift service - //! @param region Specifies the AWS region to use - //! @return True if client configuration succeeds, false otherwise - virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0; - - //! CreatePlayerId - //! Create a new, random ID number for every player in every new game session. - //! @param includeBrackets Whether includes brackets in player id - //! @param includeDashes Whether includes dashes in player id - //! @return The player id to use in game session - virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0; - }; - - // IAWSGameLiftRequests EBus wrapper for scripting - class AWSGameLiftRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftRequestBus = AZ::EBus; - - // ISessionAsyncRequests EBus wrapper for scripting - class AWSGameLiftSessionAsyncRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftSessionAsyncRequestBus = AZ::EBus; - - // ISessionRequests EBus wrapper for scripting - class AWSGameLiftSessionRequests - : public AZ::EBusTraits - { - public: - using MutexType = AZStd::recursive_mutex; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - using AWSGameLiftSessionRequestBus = AZ::EBus; - - // IMatchmakingAsyncRequests EBus wrapper for scripting + // IMatchmakingAsyncRequests EBus wrapper class AWSGameLiftMatchmakingAsyncRequests : public AZ::EBusTraits { @@ -84,7 +25,7 @@ namespace AWSGameLift }; using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus; - // IMatchmakingRequests EBus wrapper for scripting + // IMatchmakingRequests EBus wrapper class AWSGameLiftMatchmakingRequests : public AZ::EBusTraits { @@ -121,7 +62,7 @@ namespace AWSGameLift virtual void StopPolling() = 0; }; - // IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting + // IAWSGameLiftMatchmakingEventRequests EBus wrapper class AWSGameLiftMatchmakingEventRequests : public AZ::EBusTraits { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h new file mode 100644 index 0000000000..e1951d1e61 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftRequestBus.h @@ -0,0 +1,51 @@ +/* + * 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 + +namespace AWSGameLift +{ + //! IAWSGameLiftRequests + //! GameLift Gem interfaces to configure GameLift client and other help functions, + //! like creating random GameLift player id + class IAWSGameLiftRequests + { + public: + AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}"); + + IAWSGameLiftRequests() = default; + virtual ~IAWSGameLiftRequests() = default; + + //! ConfigureGameLiftClient + //! Configure GameLift client to interact with Amazon GameLift service + //! @param region Specifies the AWS region to use + //! @return True if client configuration succeeds, false otherwise + virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0; + + //! CreatePlayerId + //! Create a new, random ID number for every player in every new game session. + //! @param includeBrackets Whether includes brackets in player id + //! @param includeDashes Whether includes dashes in player id + //! @return The player id to use in game session + virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0; + }; + + // IAWSGameLiftRequests EBus wrapper + class AWSGameLiftRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftRequestBus = AZ::EBus; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h new file mode 100644 index 0000000000..c99509ca3f --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSessionRequestBus.h @@ -0,0 +1,38 @@ +/* + * 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 AWSGameLift +{ + // ISessionAsyncRequests EBus wrapper + class AWSGameLiftSessionAsyncRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionAsyncRequestBus = AZ::EBus; + + // ISessionRequests EBus wrapper + class AWSGameLiftSessionRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionRequestBus = AZ::EBus; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp index cc2f84cb35..2e978402cd 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp @@ -97,6 +97,7 @@ namespace AWSGameLift AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str()); RequestPlayerJoinMatch(ticket, playerId); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchComplete); m_status = TicketTrackerStatus::Idle; return; } @@ -104,25 +105,28 @@ namespace AWSGameLift ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED || ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED) { - AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", - ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + AZ_Warning(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchFailure); m_status = TicketTrackerStatus::Idle; return; } else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE) { - // broadcast acceptance requires to player - AzFramework::MatchAcceptanceNotificationBus::Broadcast(&AzFramework::MatchAcceptanceNotifications::OnMatchAcceptance); + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is pending on acceptance, %s.", + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchAcceptance); } else { AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.", - ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str()); } } else { AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } } else @@ -130,11 +134,13 @@ namespace AWSGameLift AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate, describeMatchmakingOutcome.GetError().GetExceptionName().c_str(), describeMatchmakingOutcome.GetError().GetMessage().c_str()); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } } else { AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage); + AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError); } m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS)); } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h index 04bdd71c85..9fd7f76e1d 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h @@ -12,7 +12,7 @@ #include #include -#include +#include #include diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp index 224f16481e..4ee2d31ebf 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -75,7 +76,15 @@ namespace AWSGameLift bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + Aws::Client::ClientConfiguration clientConfig; + AWSCore::AwsApiJobConfig* defaultConfig = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(defaultConfig, &AWSCore::AWSCoreRequests::GetDefaultConfig); + if (defaultConfig) + { + clientConfig = defaultConfig->GetClientConfiguration(); + } + // Set up client endpoint or region AZStd::string localEndpoint = ""; #if defined(AWSGAMELIFT_DEV) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h index 1f32b69f75..8a0c91c36d 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h @@ -8,10 +8,13 @@ #pragma once +#include #include #include -#include +#include +#include +#include namespace AWSGameLift { @@ -23,22 +26,37 @@ namespace AWSGameLift struct AWSGameLiftStartMatchmakingRequest; struct AWSGameLiftStopMatchmakingRequest; - // MatchAcceptanceNotificationBus EBus handler for scripting - class AWSGameLiftMatchAcceptanceNotificationBusHandler - : public AzFramework::MatchAcceptanceNotificationBus::Handler + // MatchmakingNotificationBus EBus handler for scripting + class AWSGameLiftMatchmakingNotificationBusHandler + : public AzFramework::MatchmakingNotificationBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER( - AWSGameLiftMatchAcceptanceNotificationBusHandler, + AWSGameLiftMatchmakingNotificationBusHandler, "{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}", AZ::SystemAllocator, - OnMatchAcceptance); + OnMatchAcceptance, OnMatchComplete, OnMatchError, OnMatchFailure); void OnMatchAcceptance() override { Call(FN_OnMatchAcceptance); } + + void OnMatchComplete() override + { + Call(FN_OnMatchComplete); + } + + void OnMatchError() override + { + Call(FN_OnMatchError); + } + + void OnMatchFailure() override + { + Call(FN_OnMatchFailure); + } }; // MatchmakingAsyncRequestNotificationBus EBus handler for scripting diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp index 981c1599c9..d256c6c33c 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp @@ -65,13 +65,6 @@ namespace AWSGameLift ->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId, { { { "IncludeBrackets", "" }, { "IncludeDashes", "" } } }); - - behaviorContext->EBus("AWSGameLiftMatchmakingEventRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling, - { { { "TicketId", "" }, - { "PlayerId", "" } } }) - ->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling); } } @@ -128,7 +121,7 @@ namespace AWSGameLift if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") ->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync, { { { "AcceptMatchRequest", "" } } }) ->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync, @@ -137,20 +130,28 @@ namespace AWSGameLift { { { "StopMatchmakingRequest", "" } } }); behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") ->Handler(); behaviorContext->EBus("AWSGameLiftMatchmakingRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, { { { "AcceptMatchRequest", "" } } }) + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, + { { { "AcceptMatchRequest", "" } } }) ->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking, { { { "StartMatchmakingRequest", "" } } }) ->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking, { { { "StopMatchmakingRequest", "" } } }); - behaviorContext->EBus("AWSGameLiftMatchAcceptanceNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Handler(); + behaviorContext->EBus("AWSGameLiftMatchmakingEventRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling, + { { { "TicketId", "" }, + { "PlayerId", "" } } }) + ->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling); + + behaviorContext->EBus("AWSGameLiftMatchmakingNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking") + ->Handler(); } } @@ -166,7 +167,7 @@ namespace AWSGameLift if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("AWSGameLiftSessionAsyncRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, { { { "CreateSessionRequest", "" } } }) ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } }) @@ -175,11 +176,11 @@ namespace AWSGameLift ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync); behaviorContext->EBus("AWSGameLiftSessionAsyncRequestNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Handler(); behaviorContext->EBus("AWSGameLiftSessionRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session") ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } }) ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } }) ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } }) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h index dbac9a798a..7e17f085b3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h @@ -20,7 +20,7 @@ namespace Aws namespace AWSGameLift { - //! IAWSGameLiftRequests + //! IAWSGameLiftInternalRequests //! GameLift Gem internal interface which is used to fetch gem global GameLift client class IAWSGameLiftInternalRequests { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp index 4dc4dd85f6..be72de555e 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp @@ -82,27 +82,12 @@ protected: m_gameliftClientMockPtr.reset(); } - void WaitForProcessFinish(uint64_t expectedNum) + void WaitForProcessFinish(AZStd::function processFinishCondition) { int processingTime = 0; while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) { - if (::UnitTest::TestRunner::Instance().m_numAssertsFailed == expectedNum) - { - AZ_TEST_STOP_TRACE_SUPPRESSION(expectedNum); - return; - } - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS)); - processingTime += TEST_WAIT_BUFFER_TIME_MS; - } - } - - void WaitForProcessFinish() - { - int processingTime = 0; - while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) - { - if (m_gameliftClientTicketTracker->IsTrackerIdle()) + if (processFinishCondition()) { return; } @@ -119,19 +104,27 @@ public: TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors) { AZ::Interface::Get()->SetGameLiftClient(nullptr); + + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -144,9 +137,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -161,9 +157,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -189,13 +188,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithCompleteSta .Times(1) .WillOnce(::testing::Return(outcome)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -217,9 +218,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButNoPlayerSess .Times(1) .WillOnce(::testing::Return(outcome)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -245,14 +249,17 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButFailedToJoin .Times(1) .WillOnce(::testing::Return(outcome)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(false)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -269,9 +276,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketTimeOu .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -288,9 +296,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketFailed .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -307,9 +316,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketCancel .Times(1) .WillOnce(::testing::Return(outcome)); - AZ_TEST_START_TRACE_SUPPRESSION; + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(1); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -342,13 +352,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketComple .WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome2)); - SessionHandlingClientRequestsMock handlerMock; - EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } @@ -365,7 +377,9 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA connectionInfo.SetIpAddress("DummyIpAddress"); connectionInfo.SetPort(123); connectionInfo.AddMatchedPlayerSessions( - Aws::GameLift::Model::MatchedPlayerSession().WithPlayerId("player1").WithPlayerSessionId("playersession1")); + Aws::GameLift::Model::MatchedPlayerSession() + .WithPlayerId("player1") + .WithPlayerSessionId("playersession1")); Aws::GameLift::Model::MatchmakingTicket ticket2; ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); @@ -379,13 +393,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA .WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome2)); - MatchAcceptanceNotificationsHandlerMock handlerMock1; - EXPECT_CALL(handlerMock1, OnMatchAcceptance()).Times(1); - - SessionHandlingClientRequestsMock handlerMock2; - EXPECT_CALL(handlerMock2, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true)); + SessionHandlingClientRequestsMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(true)); + MatchmakingNotificationsHandlerMock matchmakingHandlerMock; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish(); + WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); }); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchAcceptance == 1); + ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp index 1c3e8726fd..6ce0cb8f64 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp @@ -266,6 +266,8 @@ const char* const AWSGameLiftClientManagerTest::DummyPlayerId = "dummyPlayerId"; TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AZ_TEST_START_TRACE_SUPPRESSION; auto result = m_gameliftClientManager->ConfigureGameLiftClient(""); AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message @@ -274,6 +276,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_G TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AWSResourceMappingRequestsHandlerMock handlerMock; EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2")); AZ_TEST_START_TRACE_SUPPRESSION; @@ -284,6 +288,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredenti TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult) { + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr); AWSCredentialRequestsHandlerMock handlerMock; EXPECT_CALL(handlerMock, GetCredentialsProvider()) .Times(1) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h index 01afec5c3f..d685f61d30 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h @@ -9,9 +9,9 @@ #pragma once #include -#include #include #include +#include #include #include @@ -76,21 +76,44 @@ public: MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void()); }; -class MatchAcceptanceNotificationsHandlerMock - : public AzFramework::MatchAcceptanceNotificationBus::Handler +class MatchmakingNotificationsHandlerMock + : public AzFramework::MatchmakingNotificationBus::Handler { public: - MatchAcceptanceNotificationsHandlerMock() + MatchmakingNotificationsHandlerMock() + { + AzFramework::MatchmakingNotificationBus::Handler::BusConnect(); + } + + ~MatchmakingNotificationsHandlerMock() + { + AzFramework::MatchmakingNotificationBus::Handler::BusDisconnect(); + } + + void OnMatchAcceptance() override + { + ++m_numMatchAcceptance; + } + + void OnMatchComplete() override + { + ++m_numMatchComplete; + } + + void OnMatchError() override { - AzFramework::MatchAcceptanceNotificationBus::Handler::BusConnect(); + ++m_numMatchError; } - ~MatchAcceptanceNotificationsHandlerMock() + void OnMatchFailure() override { - AzFramework::MatchAcceptanceNotificationBus::Handler::BusDisconnect(); + ++m_numMatchFailure; } - MOCK_METHOD0(OnMatchAcceptance, void()); + int m_numMatchAcceptance = 0; + int m_numMatchComplete = 0; + int m_numMatchError = 0; + int m_numMatchFailure = 0; }; class SessionAsyncRequestNotificationsHandlerMock diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake index 629d1596cf..fe22e0c65c 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake @@ -17,7 +17,9 @@ set(FILES Include/Request/AWSGameLiftSearchSessionsRequest.h Include/Request/AWSGameLiftStartMatchmakingRequest.h Include/Request/AWSGameLiftStopMatchmakingRequest.h - Include/Request/IAWSGameLiftRequests.h + Include/Request/AWSGameLiftRequestBus.h + Include/Request/AWSGameLiftSessionRequestBus.h + Include/Request/AWSGameLiftMatchmakingRequestBus.h Source/Activity/AWSGameLiftActivityUtils.cpp Source/Activity/AWSGameLiftActivityUtils.h Source/Activity/AWSGameLiftAcceptMatchActivity.cpp diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h similarity index 97% rename from Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h rename to Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h index 777086e633..27096b9fe8 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/IAWSGameLiftServerRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Include/Request/AWSGameLiftServerRequestBus.h @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #pragma once #include @@ -45,7 +45,7 @@ namespace AWSGameLift virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0; }; - // IAWSGameLiftServerRequests EBus wrapper for scripting + // IAWSGameLiftServerRequests EBus wrapper class AWSGameLiftServerRequests : public AZ::EBusTraits { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h index ee21751fe9..6e7ce4e005 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake index 9039c9943e..70dfd38fc3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake @@ -10,7 +10,7 @@ set(FILES ../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h ../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp ../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h - Include/Request/IAWSGameLiftServerRequests.h + Include/Request/AWSGameLiftServerRequestBus.h Source/AWSGameLiftServerManager.cpp Source/AWSGameLiftServerManager.h Source/AWSGameLiftServerSystemComponent.cpp diff --git a/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h b/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h index 2cd5ba97a8..3bb06acd75 100644 --- a/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h +++ b/Gems/AWSMetrics/Code/Include/Private/MetricsManager.h @@ -31,7 +31,7 @@ namespace AWSMetrics static const unsigned int DesiredMaxWorkers = 2; MetricsManager(); - ~MetricsManager(); + virtual ~MetricsManager(); //! Initializing the metrics manager //! @return Whether the operation is successful. @@ -93,6 +93,12 @@ namespace AWSMetrics //! @return Total number of requests for sending metrics events. int GetNumTotalRequests() const; + protected: + //! Send metrics to a local file. + //! @param metricsQueue metricsQueue Metrics queue that stores the metrics. + //! @return Outcome of the operation. + virtual AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue); + private: //! Job management void SetupJobContext(); @@ -112,11 +118,6 @@ namespace AWSMetrics //! @param metricsQueue Metrics events to send. void SendMetricsToServiceApiAsync(const MetricsQueue& metricsQueue); - //! Send metrics to a local file. - //! @param metricsQueue metricsQueue Metrics queue that stores the metrics. - //! @return Outcome of the operation. - AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue); - //! Push metrics events to the front of the queue for retry. //! @param metricsEventsForRetry Metrics events for retry. void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry); diff --git a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h index b77f39c93c..4f40fd6ff1 100644 --- a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h +++ b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h @@ -139,11 +139,6 @@ namespace AWSMetrics return true; } - bool RemoveDirectory(const AZStd::string& directory) - { - return AZ::IO::SystemFile::DeleteDir(directory.c_str()); - } - AZ::IO::FileIOBase* m_priorFileIO = nullptr; AZ::IO::FileIOBase* m_localFileIO = nullptr; diff --git a/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp b/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp index 3f87a1079d..9fcebec524 100644 --- a/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp +++ b/Gems/AWSMetrics/Code/Tests/MetricsManagerTest.cpp @@ -75,6 +75,23 @@ namespace AZ namespace AWSMetrics { + class MetricsManagerMock + : public MetricsManager + { + private: + AZ::Outcome SendMetricsToFile(AZStd::shared_ptr metricsQueue) override + { + if (AZ::IO::FileIOBase::GetInstance()) + { + return AZ::Success(); + } + else + { + return AZ::Failure(AZStd::string{ "Invalid File IO" }); + } + } + }; + class AWSMetricsNotificationBusMock : protected AWSMetricsNotificationBus::Handler { @@ -134,13 +151,11 @@ namespace AWSMetrics AWSMetricsGemAllocatorFixture::SetUp(); AWSMetricsRequestBus::Handler::BusConnect(); - m_metricsManager = AZStd::make_unique(); + m_metricsManager = AZStd::make_unique(); AZStd::string configFilePath = CreateClientConfigFile(true, (double) TestMetricsEventSizeInBytes / MbToBytes * 2, DefaultFlushPeriodInSeconds, 0); m_settingsRegistry->MergeSettingsFile(configFilePath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {}); m_metricsManager->Init(); - RemoveFile(m_metricsManager->GetMetricsFilePath()); - ReplaceLocalFileIOWithMockIO(); } @@ -149,8 +164,6 @@ namespace AWSMetrics RevertMockIOToLocalFileIO(); RemoveFile(GetDefaultTestFilePath()); - RemoveFile(m_metricsManager->GetMetricsFilePath()); - RemoveDirectory(m_metricsManager->GetMetricsFileDirectory()); m_metricsManager.reset(); @@ -233,7 +246,7 @@ namespace AWSMetrics } } - AZStd::unique_ptr m_metricsManager; + AZStd::unique_ptr m_metricsManager; AWSMetricsNotificationBusMock m_notifications; AZ::IO::FileIOBase* m_fileIOMock; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index 633ea85387..b53dda13aa 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -111,16 +111,18 @@ void DirectionalLightShadow::GetShadowCoords( float3 worldPosition, out float3 shadowCoords[ViewSrg::MaxCascadeCount]) { - const float4x4 depthBiasMatrices[ViewSrg::MaxCascadeCount] = - ViewSrg::m_directionalLightShadows[lightIndex].m_depthBiasMatrices; const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; - + const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias; + const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices; + const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices; + for (uint index = 0; index < cascadeCount; ++index) { - const float4x4 depthBiasMatrix = depthBiasMatrices[index]; - const float4 shadowCoordHomogeneous = mul(depthBiasMatrix, - float4(worldPosition, 1.)); - shadowCoords[index] = shadowCoordHomogeneous.xyz / shadowCoordHomogeneous.w; + float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.)); + lightSpacePos.z += shadowBias; + + const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos); + shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w; } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli index 94d6f20da3..27fa36182c 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli @@ -102,18 +102,19 @@ partial ShaderResourceGroup ViewSrg struct DirectionalLightShadow { - float4x4 m_depthBiasMatrices[MaxCascadeCount]; float4x4 m_lightViewToShadowmapMatrices[MaxCascadeCount]; float4x4 m_worldToLightViewMatrices[MaxCascadeCount]; float m_slopeBiasBase[MaxCascadeCount]; float m_boundaryScale; uint m_shadowmapSize; // width and height of shadowmap uint m_cascadeCount; + float m_shadowBias; uint m_predictionSampleCount; uint m_filteringSampleCount; uint m_debugFlags; uint m_shadowFilterMethod; float m_far_minus_near; + float3 m_padding; }; enum ShadowFilterMethod diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h index 769c7b95a6..2bba1338a1 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h @@ -157,6 +157,9 @@ namespace AZ //! Sets whether the directional shadowmap should use receiver plane bias. //! This attempts to reduce shadow acne when using large pcf filters. virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0; + + //! Reduces acne by applying a small amount of bias along shadow-space z. + virtual void SetShadowBias(LightHandle handle, float bias) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 0a9f3480ad..4774ac31a1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -589,6 +589,15 @@ namespace AZ m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable; } + void DirectionalLightFeatureProcessor::SetShadowBias(LightHandle handle, float bias) + { + for (auto& it : m_shadowData) + { + it.second.GetData(handle.GetIndex()).m_shadowBias = bias; + } + m_shadowBufferNeedsUpdate = true; + } + void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { PrepareForChangingRenderPipelineAndCameraView(); @@ -1522,10 +1531,6 @@ namespace AZ for (uint16_t cascadeIndex = 0; cascadeIndex < GetCascadeCount(handle); ++cascadeIndex) { - const Matrix4x4& worldToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetWorldToClipMatrix(); - const Matrix4x4 depthBiasMatrix = Shadow::GetClipToShadowmapTextureMatrix() * worldToLightClipMatrix; - shadowData.m_depthBiasMatrices[cascadeIndex] = depthBiasMatrix; - const Matrix4x4& lightViewToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetViewToClipMatrix(); const Matrix4x4 lightViewToShadowmapMatrix = Shadow::GetClipToShadowmapTextureMatrix() * lightViewToLightClipMatrix; shadowData.m_lightViewToShadowmapMatrices[cascadeIndex] = lightViewToShadowmapMatrix; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 3c1ff8eabd..2cf5b0b1e6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -72,12 +72,6 @@ namespace AZ // [GFX TODO][ATOM-15172] Look into compacting struct DirectionalLightShadowData struct DirectionalLightShadowData { - AZStd::array m_depthBiasMatrices = - { { - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity(), - Matrix4x4::CreateIdentity() } }; AZStd::array m_lightViewToShadowmapMatrices = { { Matrix4x4::CreateIdentity(), @@ -97,11 +91,14 @@ namespace AZ float m_boundaryScale = 0.f; uint32_t m_shadowmapSize = 1; // width and height of shadowmap uint32_t m_cascadeCount = 1; + // Reduce acne by applying a small amount of bias to apply along shadow-space z. + float m_shadowBias = 0.0f; uint32_t m_predictionSampleCount = 0; uint32_t m_filteringSampleCount = 0; uint32_t m_debugFlags = 0; uint32_t m_shadowFilterMethod = 0; float m_far_minus_near = 0; + float m_padding[3]; }; class DirectionalLightFeatureProcessor final @@ -218,6 +215,7 @@ namespace AZ void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; + void SetShadowBias(LightHandle handle, float bias) override; const Data::Instance GetLightBuffer() const; uint32_t GetLightCount() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h index a8088c63ac..9ccc4f329b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -168,6 +168,14 @@ namespace AZ //! Sets whether the directional shadowmap should use receiver plane bias. //! @param enable flag specifying whether to enable the receiver plane bias feature virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0; + + //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. + //! @return Returns the amount of bias to apply. + virtual float GetShadowBias() const = 0; + + //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. + //! @param Sets the amount of bias to apply. + virtual void SetShadowBias(float bias) = 0; }; using DirectionalLightRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index a58acc0114..20073f437c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -109,6 +109,9 @@ namespace AZ //! This uses partial derivatives to reduce shadow acne when using large pcf kernels. bool m_receiverPlaneBiasEnabled = true; + //! Reduces shadow acne by applying a small amount of offset along shadow-space z. + float m_shadowBias = 0.0f; + bool IsSplitManual() const; bool IsSplitAutomatic() const; bool IsCascadeCorrectionDisabled() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 9d384c2e24..98d3f838c0 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -38,7 +38,8 @@ namespace AZ ->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled) ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) - ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled); + ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled) + ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index 78558cfc85..c20a9f8e17 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -84,6 +84,8 @@ namespace AZ ->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount) ->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled) ->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled) + ->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias) + ->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias) ->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") @@ -98,7 +100,8 @@ namespace AZ ->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled") ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") - ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled"); + ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled") + ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias"); ; } } @@ -406,6 +409,20 @@ namespace AZ return aznumeric_cast(m_configuration.m_filteringSampleCount); } + void DirectionalLightComponentController::SetShadowBias(float bias) + { + m_configuration.m_shadowBias = bias; + if (m_featureProcessor) + { + m_featureProcessor->SetShadowBias(m_lightHandle, bias); + } + } + + float DirectionalLightComponentController::GetShadowBias() const + { + return m_configuration.m_shadowBias; + } + void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count) { const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast(count)); @@ -499,6 +516,7 @@ namespace AZ SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled); SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); + SetShadowBias(m_configuration.m_shadowBias); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index 933f2705e7..a0d552cf99 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -80,6 +80,8 @@ namespace AZ void SetFilteringSampleCount(uint32_t count) override; bool GetShadowReceiverPlaneBiasEnabled() const override; void SetShadowReceiverPlaneBiasEnabled(bool enable) override; + float GetShadowBias() const override; + void SetShadowBias(float width) override; private: friend class EditorDirectionalLightComponent; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 69ba295e9b..99e2cc1485 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -133,8 +133,8 @@ namespace AZ ->EnumAttribute(ShadowFilterMethod::Esm, "ESM") ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count", - "This is used only when the pixel is predicted as on the boundary. " + ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n", + "This is used only when the pixel is predicted as on the boundary.\n" "Specific to PCF and ESM+PCF.") ->Attribute(Edit::Attributes::Min, 4) ->Attribute(Edit::Attributes::Max, 64) @@ -142,10 +142,19 @@ namespace AZ ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) ->DataElement( Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled, - "Shadow Receiver Plane Bias Enable", + "Shadow Receiver Plane Bias Enable\n", "This reduces shadow acne when using large pcf kernels.") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); + ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) + ->DataElement( + Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_shadowBias, + "Shadow Bias\n", + "Reduces acne by applying a fixed bias along z in shadow-space.\n" + "If this is 0, no biasing is applied.") + ->Attribute(Edit::Attributes::Min, 0.f) + ->Attribute(Edit::Attributes::Max, 0.2) + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ; } } diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 191fdbd3e1..5c47c2bee8 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -92,6 +92,44 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, return 0 +def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list: + url = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(url.encode()) + cache_folder = manifest.get_o3de_cache_folder() + cache_filename = cache_folder / str(repo_sha256.hexdigest() + '.json') + + gem_list = [] + + file_name = pathlib.Path(cache_filename).resolve() + if not file_name.is_file(): + logger.error(f'Could not find cached repo json file for {repo_uri}') + return gem_list + + with file_name.open('r') as f: + try: + repo_data = json.load(f) + except json.JSONDecodeError as e: + logger.error(f'{file_name} failed to load: {str(e)}') + return gem_list + + # Get list of gems, then add all json paths to the list if they exist in the cache + repo_gems = [] + try: + repo_gems.append((repo_data['gems'], 'gem.json')) + except KeyError: + pass + + for o3de_object_uris, manifest_json in repo_gems: + for o3de_object_uri in o3de_object_uris: + manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' + manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if cache_gem_json_filepath.is_file(): + logger.warn(f'Could not find cached gem json file for {o3de_object_uri} in repo {repo_uri}') + gem_list.append(cache_gem_json_filepath) + + return gem_list + def refresh_repos() -> int: json_data = manifest.load_o3de_manifest()